React: Criando  Hooks Customizados
Tags: react, hooks, ptbr, brazilian
Published: 2020-08-11 17:09:19

Uma das características mais importantes que um código pode ter é o reuso. É bom poder encapsular funcionalidades específicas em uma classe ou função. Esse é, na minha opinião, um probleminha com componentes de classe no React. Você precisa usar métodos do ciclo de vida do componente, como `componentDidMount` e `componentWillUnmount`. Fica mais difícil encapsular tudo isso. Você precisa alterar seus componentes, mudando a hierarquia da classe, adicionando contexts com providers ou algo do tipo. Não há algo que você possa simplesmente "plugar" no seu componente.

É aqui que o uso de componentes de função pode vir a calhar, já que os Hooks e Effects do React podem ser muito bem aproveitados. O objetivo deste pequeno artigo é mostrar como você pode utilizar encapsular específicas para sua aplicação lançando mão de Hooks. Por exemplo, o componente abaixo, extraído de um caso de uso real, permite ao usuário a entidade atual (um usuário, um aluno, um professor, uma empresa, etc):

```javascript
export const FavoriteEntitiesSection = () => {
  const [favoriteEntities, setFavoriteEntities] = useState([]);
  const [currentEntityIsFavorite, setCurrentEntityIsFavorite] = useState(false);
  const currentEntity = useSelector((state) => state.currentEntity);

  const refreshFavoriteEntities = () => {
    FavoriteEntities.fetchCurrent().then(setFavoriteEntities);
  };

  useEffect(() => {
    refreshFavoriteEntities();
    return () => {
      FavoriteEntities.someCleanupAction();
    };
  }, []);

  useEffect(
    () => {
      const isFavorite = favoriteEntities.some((item) => item.id === currentEntity.id);
      setCurrentEntityIsFavorite(isFavorite);
    },
    [favoriteEntities, currentEntity],
  );

  const handlePinEntityButtonClick = () => {
    FavoriteEntities.addEntity({
      currentEntity,
    }).then(refreshFavoriteEntities);
  };

  const handleRemoveEntity = (id: string): void => {
    FavoriteEntities.removeFavoriteEntity(id).then(refreshFavoriteEntities);
  };

  return (
    <div>
      <div>Id: {currentEntity.id}</div>
      <div>Name: {currentEntity.name}</div>
      <div>
        <button disabled={currentEntityIsFavorite} onClick={handlePinEntityButtonClick}>
          Pin Entity
        </button>
      </div>
      <div>
        {favoriteEntities.map((entity) => (
          <div>{entity.name} <button onClick={() => removeEntity(entity.id)}>Remove</button></div>
        ))}
      </div>
    </div>
  );
};
```

E se nós quisermos usar esses favoritos em outro lugar? Poderíamos simplesmente incluir esse componente inteiro, mas pode ser que nós queiramos somente a listagem, pode ser que queiramos somente saber se a entidade atual está nos favoritos, etc. Isso pode ser resolvido incluindo tudo no Redux? Sim, mas nem sempre isso é desejável. Vamos refatorar esse código para que a funcionalidade de "favoritos" fique encapsulada:

```javascript
function useFavoriteEntities(loadAtMount = true) {
  const [favoriteEntities, setFavoriteEntities] = useState([]);
  const [currentEntityIsFavorite, setCurrentEntityIsFavorite] = useState(false);
  const [isRefreshing, setIsRefreshing] = useState(false);
  const currentEntity = useSelector((state) => state.currentEntity);

  const refreshFavoriteEntities = () => {
    if (!isRefreshing) {
      setIsRefreshing(true);
      FavoriteEntities.fetchCurrent()
        .then(setFavoriteEntities)
        .finally(() => setIsRefreshing(false));
    }
  };

  useEffect(() => {
    if (loadAtMount) {
      refreshFavoriteEntities();
    }
    return () => {
      FavoriteEntities.someCleanupAction();
    };
  }, []);

  useEffect(
    () => {
      const isFavorite = favoriteEntities.some((item) => item.id === currentEntity.id);
      setCurrentEntityIsFavorite(isFavorite);
    },
    [favoriteEntities, currentEntity],
  );

  const saveCurrentEntity = () => {
    if (!currentEntityIsFavorite) {
      FavoriteEntities.addEntity({
        currentEntity,
      }).then(refreshFavoriteEntities);
    }
  };

  const removeEntity = (id) => {
    if (currentEntityIsFavorite) {
      FavoriteEntities.removeEntity({
        id,
      }).then(refreshFavoriteEntities);
    }
  };

  return {
    favoriteEntities,
    refreshFavoriteEntities,
    currentEntityIsFavorite,
    saveCurrentEntity,
    removeEntity,
  };
}

export const FavoriteEntitiesSection = () => {
  const {
    favoriteEntities,
    currentEntityIsFavorite,
    saveCurrentEntity,
    removeEntity,
  } = useFavoriteEntities(true);

  const currentEntity = useSelector((state) => state.currentEntity);


  const handlePinEntityButtonClick = () => {
    saveCurrentEntity();
  };

  const handleRemoveEntity = (id: string): void => {
    removeEntity(id);
  };

  return (
    <div>
      <div>Id: {currentEntity.id}</div>
      <div>Name: {currentEntity.name}</div>
      <div>
        <button disabled={currentEntityIsFavorite} onClick={handlePinEntityButtonClick}>
          Pin Entity
        </button>
      </div>
      <div>
        {favoriteEntities.map((entity) => (
          <div>{entity.name} <button onClick={() => handleRemoveEntity(entity.id)}>Remove</button></div>
        ))}
      </div>
    </div>
  );
};
```

Perceba como o componente deixa de se preocupar com a lógica em torno dos favoritos, inclusive com a atualização automática: tudo é feito dentro da função. Fica até mais fácil dar uma incrementada nas funcionalidades, como eu fiz no exemplo acima:

* Chamadas repetidas ao `refreshFavoriteEntities` não vão gerar chamadas desnecessárias ao serviço.
* Tentativas de salvar um favorito que já foi salvo não geram duplicações

Além disso. se em algum outro componente precisar somente da listagem, basta usar:

```javascript
const { favoriteEntities } = useFavoriteEntities();
```

Espero que esse exemplo ajude a entender como tirar vantagem dos Hooks no React! Afinal, não pode ser só choro e ranger de dentes, de vez em quando tem que haver alguma vantagem.