React Hooks: Como utilizar, motivações e exemplos práticos
A funcionalidade de Hooks trazida a partir da versão 16.7.0 do React visa basicamente oferecer formas de trabalharmos com estado e outras API's sem a necessidade de ter uma classe (stateful component).
Antes de falarmos mais sobre as motivações por trás dessa feature ou até mostrar exemplos mais complexos de como os hooks vão ajudar no nosso dia-a-dia, vamos entender um exemplo básico com um pouco de código.
Imagine que você tenha um Modal, no clique de um botão o modal deve aparecer, no clique de outro botão esse modal deve desaparecer. Utilizando a abordagem tradicional do React teríamos algo assim:
class App extends Component {
state = {
modalOpen: false,
}
render() {
return (
<div>
<button onClick={() => this.setState({ modalOpen: true })}>
Exibir modal
</button>
<button onClick={() => this.setState({ modalOpen: false })}>
Remover modal
</button>
{ this.state.modalOpen && <Modal /> }
</div>
);
}
}
Essa seria a abordagem mínima para conseguirmos controlar a aparição do modal em tela utilizando o estado do React. Quando utilizamos os hooks nós simplesmente evitamos a verbosidade desnecessária que toda essa criação do state nos dá, ficando com um código como o seguinte:
import { useState } from 'react';
const App = () => {
const [modalOpen, setModalOpen] = useState(false);
return (
<div>
<button onClick={() => setModalOpen(true)}>
Exibir modal
</button>
<button onClick={() => setModalOpen(false)}>
Remover modal
</button>
{ modalOpen && <Modal /> }
</div>
);
}
Veja que utilizamos o hook useState
para declarar um estado em um componente criado em formato de função. A função useState
recebe por parâmetro o valor padrão do nosso estado e retorna o valor do estado em si e uma função que altera esse valor.
Motivações
Até então, a forma mais tradicional de compartilharmos funcionamento entre componentes era pelos patterns já conhecidos, os HOC's (Higher-order components) e as render props.
O grande problema desses padrões é que você precisa modificar boa parte do código do componente para que o mesmo se adapte ao funcionamento compartilhado, aumentando sua verbosidade e perdendo boa parte do isolamento de responsabilidade, ou seja, você acaba perdendo qual parte do código faz o que.
Veja um exemplo de utilização de dois render props em um único componente:
export default class extends Component {
render() {
return (
<Theme>
{theme => (
<I18N>
{i18n => (
<Header theme={theme} language={i18n.getLanguage()} />
)}
</I18N>
)}
</Theme>
);
}
}
A verbosidade desse pattern torna o código praticamente ilegível, ainda mais se tivermos um crescimento de responsabilidades desse componente com o tempo.
Além disso, os dois patterns utilizados anteriormente acabavam criando muitos níveis na renderização final do React tornando impossível uma visualização de árvore para encontrar o que é o que, veja um exemplo do site da Netflix que utiliza React ainda sem os hooks:
Veja que os elementos AppContextProvider
, ConnectToApps
, LayoutContext
, DetectFonts
são tudo fruto de contextos criados através dos patterns de HOC's e Render Props (com o hooks não precisaríamos de nenhum deles).
Existem outras motivações por trás do hook mas a maioria gira na complexidade e verbosidade desnecessária em torno dos patterns já existentes principalmente quando os componentes ficam maiores.
Exemplos
Para entendermos na prática como os hooks funcionam vamos analisar alguns conceitos simples de utilização desse novo pattern:
useState
O hook mais comum utilizado para controlarmos alguma variável de estado dentro de um functional component no React. Para utilizar definimos:
const [count, setCount] = useState(0);
O primeiro valor count
representa o valor do estado que será manipulado pela função setCount
recebida através da desestruturação realizada no useState
. O valor 0
repassado ao hook é o valor inicial do estado.
Então, para manipularmos o valor de count
podemos simplesmente executar:
<button onClick={() => setCount(count + 1)}>+</button>
useEffect
Uma das grandes deficiência dos funcional components sempre foi lidar com side-effects como chamadas à API, tarefas assíncronas, modificações na DOM, etc. Com o hook useEffect
podemos operar efeitos colaterais durante a renderização do nosso componente.
Imagine que no exemplo acima do count
gostaríamos de atualizar o título de página toda vez que a informação de count atualizar. Dentro do nosso funcional component (antes do return
) definimos:
useEffect(() => {
document.title = `Você clicou ${count} vezes.`
}, [count])
Veja que o hook recebe como primeiro parâmetro uma função (assíncrona ou não) que é executada após inicialização e atualização do componente, mais ou menos o que temos com o componentDidMount
e componentDidUpdate
no stateful component.
O segundo parâmetro [count]
indica em quais situações esse effect deve executar, nesse caso ele só executará caso o valor de count
alterar. Podemos também não repassar esse parâmetro e indicaremos ao hook que deve executar na inicialização e em todas atualizações do componente, independente de seus valores alterarem.
Outra dica é que você pode passar um array vazio []
ao hook como segundo parâmetro garantindo que o mesmo irá executar apenas uma vez na inicialização do componente (tipo um componentDidMount
):
const Repositories = () => {
const [repositories, setRepositories] = useState([]);
useEffect(() => {
async function loadRepositories() {
const response = await axios.get('https://api.github.com/orgs/rocketseat/repos');
setRepositories(response.data);
}
loadRepositories();
}, []);
return (...);
};
Veja que criamos uma nova função loadRepositories
dentro do useEffect
, isso se deve basicamente porque não é uma boa prática adicionarmos um async
à função que o useEffect
recebe como parâmetro.
useContext
Para utilizarmos a Context API do React até então precisávamos utilizar o pattern de render props criando um código como o seguinte:
<ThemeContext.Consumer>
{ theme => (
// Aqui temos o tema
) }
</ThemeContext.Consumer>
Mas agora com os hooks podemos simplesmente fazer o seguinte:
const theme = useContext(ThemeContext)
Quer mais?
Existem vários outros hooks nativos do React e outros milhões já criados pela comunidade, se quiser continuar lendo sobre alguns exemplos veja:
- https://reactjs.org/docs/hooks-reference.html#additional-hooks
- https://github.com/rehooks/awesome-react-hooks
Morte do Redux?
Com a vinda dos hooks, e do hook useReducer novamente surgiram boatos sobre o fim do Redux, mas calma, isso não vai acontecer. Inclusive o criador do Redux está diretamente envolvido com a criação desse pattern de hooks e já estão provendo uma API para lidar com hooks :)
Eu já falei isso antes, mas é bom reforçar, o Redux não é uma biblioteca de compartilhamento de estado apenas, e sim uma ferramenta de gerenciamento de estado para organizar o fluxo de operações e side-effects que acontece em sua aplicação o tempo todo.
Partiu hooks?
Impossível que você chegou até aqui e não esteja com vontade de botar os hooks em ação. Eu particularmente achei fantástica essa nova API e não tenho dúvidas que todo projeto deve começar a utilizar esse novo pattern o quanto antes!
Ah, não esquece de deixar um comentário aí em baixo sobre o que você achou desse post e também desse novo conceito do React :)