Utilizando o useEffect ao seu favor com useEffectByStatus
Rodolfo Della Violla
Posted on August 18, 2021
Sou desenvolvedor em uma empresa que utiliza React e Relay no frontend, consumindo API's em GraphQL. Na aplicação, existe um hook customizado chamado useMutation
, que me retorna uma função para executar a mutation, além de o status atualizado da mesma e os dados retornados por ela. Esse status é utilizado pra colocar a página em estado de loading, por exemplo.
Pois bem, vamos supor que eu preciso utilizar esse status pra realizar ações na minha página. Caso estivesse utilizando diretamente o retorno da mutation, poderia fazer algo assim:
const mutation = graphql`
mutation addUserMutation($input: AddUserInput!) {
user {
addUser(input: $input) {
user {
id
name
}
responseEnum
}
}
}
`;
const Page = () => {
const [notification, setNotification] = useState(null);
const [pageStatus, setPageStatus] = useState('idle');
const [name, setName] = useState('');
const { submit } = useMutation(mutation);
const submitMutation = async () => {
setNotification(null);
setPageStatus('loading');
const response = await submit({ name });
if (response === 'resolved') {
setName('');
// update parent component table
}
if (response === 'error') {
setNotification({ error: response.user.addUser.responseEnum });
}
setPageStatus('idle');
}
}
Isso funciona bem e é um bom exemplo de uso da programação imperativa. Mas como gosto de usar ao máximo os retornos desse hook criado pelos meus colegas, normalmente utilizo desta forma:
const Page = () => {
const [notification, setNotification] = useState(null);
const [pageStatus, setPageStatus] = useState('idle');
const [name, setName] = useState('');
const { submit, status, data } = useMutation(mutation);
useEffect(() => {
if (status === 'idle') {
setNotification(null);
setPageStatus('idle');
}
if (status === 'loading') {
setNotification(null);
setPageStatus('loading');
}
if (status === 'resolved') {
setPageStatus('idle');
setName('');
// update parent component table
}
if (status === 'error') {
setNotification({ error: data.user.addUser.responseEnum });
setPageStatus('idle');
}
}, [status]);
const submitMutation = () => submit({ name });
}
A função submitMutation
agora apenas chama a função submit
do useMutation
, deixando que o useEffect
que está enxergando as alterações de estado do status faça a maior parte do trabalho.
Porém, além de querer tornar esse useEffect
menos repetitivo, eu queria criar algo que pudesse ser utilizado em outras páginas, pois o hook useMutation
é chamado em diversos lugares da aplicação. Além disso, sempre busco deixar meu código mais elegante, pois acredito que um código estéticamente interessante torna a leitura mais prazerosa e a manutenção mais tranquila. Por fim, tenho buscado uma abordagem menos imperativa, utilizando mais o conceito de programação declarativa que permeia o React.
Pensando em tudo isso, nasce o useEffectByStatus
:
type StatusType = 'idle' | 'loading' | 'resolved' | 'error';
type UseEffectByStatusProps = {
status: StatusType;
effects: {
[key in StatusType]?: () => void;
};
};
const useEffectByStatus = (props: UseEffectByStatusProps) => {
const { status, effects } = props;
useEffect(() => {
effects[status]?.();
}, [status]);
};
Basicamente, a ideia é que ele contenha um useEffect
que dispare uma função de acordo com o status atual passado para dentro do hook. O optional chaining
é usado pra validar se foi passada uma função effect como parâmetro para o status em que o hook se encontra, pois não é obrigatória a passagem de uma função para todos os status existentes. Além disso, com a aplicação do padrão Object Literal
, não há necessidade de condicionais (if
ou switch case
).
Com a utilização deste novo hook, a página ficou assim:
const Page = () => {
const [notification, setNotification] = useState(null);
const [pageStatus, setPageStatus] = useState('idle');
const [name, setName] = useState('');
const { submit, status, data } = useMutation(mutation);
useEffectByStatus({
status,
effects: {
idle: () => {
setNotification(null);
setPageStatus('idle');
},
loading: () => {
setNotification(null);
setPageStatus('loading');
},
resolved: () => {
setPageStatus('idle');
setName('');
// update parent component table
},
error: () => {
setNotification({ error: data.user.addUser.responseEnum });
setPageStatus('idle');
},
}
});
const submitMutation = () => submit({ name });
}
Com ajuda do meu Tech Lead e parceiro @samuelmpinho
, o hook se tornou ainda mais genérico, pois passou a sugerir através do Typescript as possíveis opções de effects de acordo com o status passado no primeiro parâmetro, o que só é possível pelo uso do Object Literal
:
type UseEffectByStatusProps<T extends string> = {
status: T;
effects: {
[key in T]?: () => void;
};
};
const useEffectByStatus = <T extends string>(props: UseEffectByStatusProps<T>) => {
const { status, effects } = props;
useEffect(() => {
effects[status]?.();
}, [status]);
};
O resultado é um autocomplete
adaptável ao status passado para o hook:
Se tiverem sugestões de como melhorar ou outras aplicações deste hook, vou ficar feliz em saber! Me manda uma mensagem no Twitter pra gente conversar. Até a próxima! 🚀
Posted on August 18, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.