DEV Community

Cover image for Utilizando o useEffect ao seu favor com useEffectByStatus
Rodolfo Della Violla
Rodolfo Della Violla

Posted on

2 2

Utilizando o useEffect ao seu favor com useEffectByStatus

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');
  }
}
Enter fullscreen mode Exit fullscreen mode

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 });
}
Enter fullscreen mode Exit fullscreen mode

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]);
};
Enter fullscreen mode Exit fullscreen mode

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 });
}
Enter fullscreen mode Exit fullscreen mode

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]);
};
Enter fullscreen mode Exit fullscreen mode

O resultado é um autocomplete adaptável ao status passado para o hook:
Autocomplete com as funções effect de acordo com o status

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! 🚀

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

SurveyJS custom survey software

JavaScript Form Builder UI Component

Generate dynamic JSON-driven forms directly in your JavaScript app (Angular, React, Vue.js, jQuery) with a fully customizable drag-and-drop form builder. Easily integrate with any backend system and retain full ownership over your data, with no user or form submission limits.

Learn more

Best practices for optimal infrastructure performance with Magento

Running a Magento store? Struggling with performance bottlenecks? Join us and get actionable insights and real-world strategies to keep your store fast and reliable.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️