Usando Callbacks em Hooks Customizados do React
Tags: react, javascript, webdev
Published: 2024-03-04 12:58:04

Tenho criado frequentemente hooks customizados, seja para reutilização de lógica ou extração de responsabilidade.

Há algum tempo, meu time deparou com o seguinte dilema: "como comunicar ao componente que utiliza um hook que um processo assíncrono terminou?".

Para entender melhor o cenário, veja o hook abaixo como exemplo:

```ts
function useApplyCoupon() {
  const [isApplying, setApplying] = useState(false);
  const { id } = useOrder();
  const { mutate } = useRemoteOrder();
  const { api } = useInfra();

  const applyCoupon = async (code: string) => {
    try {
      setApplying(true);
      await api.put(`.../orders/${id}/coupon`, { code });
      await mutate();
    } catch (error) {
      handleServerError(error);
    } finally {
      setApplying(false);
    }
  };

  return { applyCoupon, isApplying };
};
```

A utilização fica da seguinte forma:

```tsx
type FormValues = {
  code: string;
};

function ProductInformation() {
  const methods = useForm<FormValues>();
  const { applyCoupon, isApplying } = useApplyCoupon();

  const handleSubmit = ({ code }: FormValues) => {
    applyCoupon(code);
  };

  return (
    <form onSubmit={methods.handleSubmit(handleSubmit)}>
      <TextInput
        id="code"
        placeholder="Inserir cupom"
        error={methods.formState.errors.code?.message}
        size="xs"
        sx={{ flex: 1 }}
        {...methods.register('code', { required: true })}
      />
      <Button size="xs" type="submit" loading={isApplying}>
        Aplicar
      </Button>
    </form>
  );
}
```

Agora, pense que após a ação de aplicar o cupom, queremos avisar o usuário se deu certo ou não.

Antes de eu compartilhar como meu time resolveu, deixem uma reação no post, pra eu saber se é um conteúdo relevante 🥺

Na época, a primeira tarefa com essa questão foi pega pelo [Evandro](https://www.linkedin.com/in/evandro-j-b72934127/), e ele resolveu de maneira muito simples, tornando-se um padrão nos nossos custom hooks, o que me motivou a compartilhar aqui.

Veja abaixo:

```ts
type Props = {
  onError?: () => void;
  onSuccess?: () => void;
};

function useApplyCoupon({ onError, onSuccess }: Props = {}) {
  const [isApplying, setApplying] = useState(false);
  const { id } = useOrder();
  const { mutate } = useRemoteOrder();
  const { api } = useInfra();

  const applyCoupon = async (code: string) => {
    try {
      setApplying(true);
      await api.put(`.../orders/${id}/coupon`, { code });
      await mutate();
      onSuccess?.();
    } catch (error) {
      if (axios.isAxiosError(error) && error.response?.status === 422) {
        onError?.();
        return;
      }
      handleServerError(error);
    } finally {
      setApplying(false);
    }
  };

  return { applyCoupon, isApplying };
};
```

Utilização:

```tsx
type FormValues = {
  code: string;
};

function ProductInformation() {
  const [isOpenModal, { open: openModal }] = useDisclosure();
  const methods = useForm<FormValues>();
  const { applyCoupon, isApplying } = useApplyCoupon({
    onSuccess() {
      openModal();
      confetti({
        origin: { y: 0.6 },
        particleCount: 100,
        spread: 70,
        zIndex: 2000,
      });
    },
    onError() {
      methods.setFocus('code');
      methods.setError('code', {
        type: 'manual',
        message: 'O código do cupom inserido não é válido. Tente outro.',
      });
    },
  });

  const handleSubmit = ({ code }: FormValues) => {
    applyCoupon(code);
  };

  return (
    <form onSubmit={methods.handleSubmit(handleSubmit)}>
      <TextInput
        id="code"
        placeholder="Inserir cupom"
        error={methods.formState.errors.code?.message}
        size="xs"
        sx={{ flex: 1 }}
        {...methods.register('code', { required: true })}
      />
      <Button size="xs" type="submit" loading={isApplying}>
        Aplicar
      </Button>
    </form>
  );
}
```

O que achou? Provavelmente existem outras formas de resolver, e eu gostaria de saber como você faria. Deixe nos comentários.