Hooks React úteis que você pode usar em seus projetos

Publicados: 2022-03-10
Resumo rápido ↬ Os componentes baseados em classe React são confusos, confusos, difíceis para humanos e máquinas. Mas antes do React 16.8, componentes baseados em classes eram obrigatórios para qualquer projeto que exigisse estados, métodos de ciclo de vida e muitas outras funcionalidades importantes. Tudo isso mudou com a introdução de ganchos no React 16.8. Ganchos mudam o jogo. Eles simplificaram o React, tornaram-no mais limpo, mais fácil de escrever e depurar e também reduziram a curva de aprendizado.

Hooks são simplesmente funções que permitem que você se conecte ou faça uso de recursos do React. Eles foram introduzidos no React Conf 2018 para resolver três grandes problemas de componentes de classe: wrapper hell, componentes enormes e classes confusas. Hooks dão poder aos componentes funcionais do React, tornando possível desenvolver uma aplicação inteira com ele.

Os problemas acima mencionados de componentes de classe estão conectados e resolver um sem o outro pode introduzir mais problemas. Felizmente, os hooks resolveram todos os problemas de forma simples e eficiente enquanto criavam espaço para recursos mais interessantes no React. Hooks não substituem conceitos e classes já existentes do React, eles apenas fornecem uma API para acessá-los diretamente.

A equipe do React introduziu vários ganchos no React 16.8. No entanto, você também pode usar ganchos de provedores de terceiros em seu aplicativo ou até mesmo criar um gancho personalizado. Neste tutorial, veremos alguns ganchos úteis no React e como usá-los. Passaremos por vários exemplos de código de cada gancho e também exploraremos como você criaria um gancho personalizado.

Nota: Este tutorial requer uma compreensão básica de Javascript (ES6+) e React.

Mais depois do salto! Continue lendo abaixo ↓

Motivação por trás dos ganchos

Como dito anteriormente, os ganchos foram criados para resolver três problemas: inferno do wrapper, componentes enormes e classes confusas. Vamos dar uma olhada em cada um deles com mais detalhes.

Inferno do invólucro

Aplicativos complexos construídos com componentes de classe são facilmente executados no inferno do wrapper. Se você examinar o aplicativo no React Dev Tools, notará componentes profundamente aninhados. Isso torna muito difícil trabalhar com os componentes ou depurá-los. Embora esses problemas possam ser resolvidos com componentes de ordem superior e props de renderização , eles exigem que você modifique um pouco seu código. Isso pode levar a confusão em um aplicativo complexo.

Hooks são fáceis de compartilhar, você não precisa modificar seus componentes antes de reutilizar a lógica.

Um bom exemplo disso é o uso do Redux connect Higher Order Component (HOC) para assinar a loja Redux. Como todos os HOCs, para usar o connect HOC, você precisa exportar o componente junto com as funções de ordem superior definidas. No caso de connect , teremos algo desta forma.

 export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

Onde mapStateToProps e mapDispatchToProps são funções a serem definidas.

Considerando que na era Hooks, pode-se facilmente obter o mesmo resultado de forma clara e sucinta usando os ganchos useSelector e useDispatch do Redux.

Componentes Enormes

Os componentes de classe geralmente contêm efeitos colaterais e lógica com estado. À medida que o aplicativo cresce em complexidade, é comum que o componente se torne confuso e confuso. Isso ocorre porque os efeitos colaterais devem ser organizados por métodos de ciclo de vida em vez de funcionalidade. Embora seja possível dividir os componentes e torná-los mais simples, isso geralmente introduz um nível mais alto de abstração.

Os ganchos organizam os efeitos colaterais por funcionalidade e é possível dividir um componente em partes com base na funcionalidade.

Aulas confusas

Classes são geralmente um conceito mais difícil do que funções. Os componentes baseados em classe do React são detalhados e um pouco difíceis para iniciantes. Se você é novo em Javascript, você pode achar funções mais fáceis de começar por causa de sua sintaxe leve em comparação com classes. A sintaxe pode ser confusa; às vezes, é possível esquecer de vincular um manipulador de eventos que poderia quebrar o código.

O React resolve esse problema com componentes funcionais e ganchos, permitindo que os desenvolvedores se concentrem no projeto em vez da sintaxe do código.

Por exemplo, os dois componentes React a seguir produzirão exatamente o mesmo resultado.

 import React, { Component } from "react"; export default class App extends Component { constructor(props) { super(props); this.state = { num: 0 }; this.incrementNumber = this.incrementNumber.bind(this); } incrementNumber() { this.setState({ num: this.state.num + 1 }); } render() { return ( <div> <h1>{this.state.num}</h1> <button onClick={this.incrementNumber}>Increment</button> </div> ); } }
 import React, { useState } from "react"; export default function App() { const [num, setNum] = useState(0); function incrementNumber() { setNum(num + 1); } return ( <div> <h1>{num}</h1> <button onClick={incrementNumber}>Increment</button> </div> ); }

O primeiro exemplo é um componente baseado em classe, enquanto o segundo é um componente funcional. Embora este seja um exemplo simples, observe como o primeiro exemplo é falso comparado ao segundo.

A Convenção e Regras de Hooks

Antes de se aprofundar nos vários ganchos, pode ser útil dar uma olhada na convenção e nas regras que se aplicam a eles. Aqui estão algumas das regras que se aplicam aos ganchos.

  1. A convenção de nomenclatura dos ganchos deve começar com o prefixo use . Assim, podemos ter useState , useEffect , etc. Se você estiver usando editores de código modernos como Atom e VSCode, o plugin ESLint pode ser um recurso muito útil para hooks React. O plug-in fornece avisos e dicas úteis sobre as melhores práticas.
  2. Os ganchos devem ser chamados no nível superior de um componente, antes da instrução de retorno. Eles não podem ser chamados dentro de uma instrução condicional, loop ou funções aninhadas.
  3. Hooks devem ser chamados de uma função React (dentro de um componente React ou outro hook). Não deve ser chamado de uma função Vanilla JS.

O gancho useState

O gancho useState é o gancho React mais básico e útil. Assim como outros hooks embutidos, este hook deve ser importado do react para ser usado em nossa aplicação.

 import {useState} from 'react'

Para inicializar o estado, devemos declarar o estado e sua função de atualização e passar um valor inicial.

 const [state, updaterFn] = useState('')

Somos livres para chamar nosso estado e função de atualização como quisermos, mas por convenção, o primeiro elemento da matriz será nosso estado, enquanto o segundo elemento será a função de atualização. É uma prática comum prefixar nossa função de atualização com o prefixo definido seguido pelo nome do nosso estado na forma de camel case.

Por exemplo, vamos definir um estado para manter os valores de contagem.

 const [count, setCount] = useState(0)

Observe que o valor inicial do nosso estado de count é definido como 0 e não como uma string vazia. Em outras palavras, podemos inicializar nosso estado para qualquer tipo de variável JavaScript, ou seja, número, string, boolean, array, objeto e até BigInt. Há uma clara diferença entre definir estados com o gancho useState e estados de componentes baseados em classe. Vale ressaltar que o hook useState retorna um array, também conhecido como variáveis ​​de estado e no exemplo acima, desestruturamos o array em state e a função updater .

Rerenderizando Componentes

Definir estados com o gancho useState faz com que o componente correspondente seja renderizado novamente. No entanto, isso só acontece se o React detectar uma diferença entre o estado anterior ou antigo e o novo estado. O React faz a comparação de estado usando o algoritmo Javascript Object.is .

Configurando Estados com useState

Nosso estado count pode ser definido para novos valores de estado simplesmente passando o novo valor para a função de atualização setCount como segue setCount(newValue) .

Este método funciona quando não queremos referenciar o valor do estado anterior. Se quisermos fazer isso, precisamos passar uma função para a função setCount .

Supondo que queremos adicionar 5 à nossa variável de count sempre que um botão for clicado, poderíamos fazer o seguinte.

 import {useState} from 'react' const CountExample = () => { // initialize our count state const [count, setCount] = useState(0) // add 5 to to the count previous state const handleClick = () =>{ setCount(prevCount => prevCount + 5) } return( <div> <h1>{count} </h1> <button onClick={handleClick}>Add Five</button> </div> ) } export default CountExample

No código acima, primeiro importamos o gancho useState do react e, em seguida, inicializamos o estado de count com um valor padrão de 0. Criamos um manipulador onClick para incrementar o valor de count em 5 sempre que o botão for clicado. Em seguida, exibimos o resultado em uma tag h1 .

Configurando Arrays e Estados de Objetos

Os estados para arrays e objetos podem ser definidos da mesma maneira que outros tipos de dados. No entanto, se quisermos manter os valores já existentes, precisamos usar o operador de dispersão ES6 ao definir os estados.

O operador spread em Javascript é usado para criar um novo objeto a partir de um objeto já existente. Isso é útil aqui porque o React compara os estados com a operação Object.is e então rerenderiza de acordo.

Vamos considerar o código abaixo para definir estados ao clicar no botão.

 import {useState} from 'react' const StateExample = () => { //initialize our array and object states const [arr, setArr] = useState([2, 4]) const [obj, setObj] = useState({num: 1, name: 'Desmond'}) // set arr to the new array values const handleArrClick = () =>{ const newArr = [1, 5, 7] setArr([...arr, ...newArr]) } // set obj to the new object values const handleObjClick = () =>{ const newObj = {name: 'Ifeanyi', age: 25} setObj({...obj, ...newObj}) } return( <div> <button onClick ={handleArrClick}>Set Array State</button> <button onClick ={handleObjClick}>Set Object State</button> </div> ) } export default StateExample

No código acima, criamos dois estados arr e obj e os inicializamos para alguns valores de array e objeto, respectivamente. Em seguida, criamos manipuladores onClick chamados handleArrClick e handleObjClick para definir os estados da matriz e do objeto, respectivamente. Quando handleArrClick acionado, chamamos setArr e usamos o operador spread ES6 para espalhar valores de array já existentes e adicionar newArr a ele.

Fizemos a mesma coisa para o manipulador handleObjClick . Aqui chamamos setObj , espalhamos os valores de objetos existentes usando o operador de espalhamento ES6 e atualizamos os valores de name e age .

Natureza assíncrona de useState

Como já vimos, definimos os estados com useState passando um novo valor para a função de atualização. Se o atualizador for chamado várias vezes, os novos valores serão adicionados a uma fila e a nova renderização será feita de acordo usando a comparação JavaScript Object.is .

Os estados são atualizados de forma assíncrona. Isso significa que o novo estado é adicionado primeiro a um estado pendente e, posteriormente, o estado é atualizado. Portanto, você ainda pode obter o valor do estado antigo se acessar o estado imediatamente em que ele for definido.

Vamos considerar o exemplo a seguir para observar esse comportamento.

No código acima, criamos um estado de count usando o gancho useState . Em seguida, criamos um manipulador onClick para incrementar o estado de count sempre que o botão for clicado. Observe que, embora o estado de count tenha aumentado, conforme exibido na tag h2 , o estado anterior ainda é registrado no console. Isso se deve à natureza assíncrona do gancho.

Se quisermos obter o novo estado, podemos tratá-lo da mesma forma que trataríamos de funções assíncronas. Aqui está uma maneira de fazer isso.

Aqui, armazenamos newCountValue criado para armazenar o valor de contagem atualizado e, em seguida, definimos o estado de count com o valor atualizado. Em seguida, registramos o valor de contagem atualizado no console.

O gancho useEffect

useEffect é outro gancho React importante usado na maioria dos projetos. Ele faz uma coisa semelhante aos métodos de ciclo de vida componentDidMount , componentWillUnmount e componentDidUpdate do componente baseado em classe. useEffect nos oferece a oportunidade de escrever códigos imperativos que podem ter efeitos colaterais no aplicativo. Exemplos de tais efeitos incluem registro, assinaturas, mutações, etc.

O usuário pode decidir quando o useEffect será executado, porém, se não estiver definido, os efeitos colaterais serão executados em cada renderização ou rerenderização.

Considere o exemplo abaixo.

 import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ console.log(count) }) return( <div> ... </div> ) }

No código acima, simplesmente registramos count no useEffect . Isso será executado após cada renderização do componente.

Às vezes, podemos querer executar o gancho uma vez (na montagem) em nosso componente. Podemos conseguir isso fornecendo um segundo parâmetro para o gancho useEffect .

 import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ setCount(count + 1) }, []) return( <div> <h1>{count}</h1> ... </div> ) }

O gancho useEffect tem dois parâmetros, o primeiro parâmetro é a função que queremos executar enquanto o segundo parâmetro é um array de dependências. Se o segundo parâmetro não for fornecido, o gancho será executado continuamente.

Ao passar um colchete vazio para o segundo parâmetro do gancho, instruímos o React a executar o gancho useEffect apenas uma vez, na montagem. Isso exibirá o valor 1 na tag h1 porque a contagem será atualizada uma vez, de 0 a 1, quando o componente for montado.

Também podemos fazer com que nosso efeito colateral seja executado sempre que alguns valores dependentes forem alterados. Isso pode ser feito passando esses valores na lista de dependências.

Por exemplo, podemos fazer com que useEffect seja executado sempre que a count for alterada da seguinte maneira.

 import { useState, useEffect } from "react"; const App = () => { const [count, setCount] = useState(0); useEffect(() => { console.log(count); }, [count]); return ( <div> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; export default App;

O useEffect acima será executado quando uma dessas duas condições for atendida.

  1. Na montagem — depois que o componente é renderizado.
  2. Quando o valor de count muda.

Na montagem, a expressão console.log será executada e a count de log será 0. Uma vez que a count seja atualizada, a segunda condição é atendida, então o useEffect é executado novamente, isso continuará sempre que o botão for clicado.

Uma vez que fornecemos o segundo argumento para useEffect , espera-se que passemos todas as dependências para ele. Se você tiver o ESLINT instalado, ele mostrará um erro de lint se alguma dependência não for passada para a lista de parâmetros. Isso também pode fazer com que o efeito colateral se comporte de forma inesperada, especialmente se depender dos parâmetros que não são passados.

Limpando o efeito

useEffect também nos permite limpar recursos antes que o componente seja desmontado. Isso pode ser necessário para evitar vazamentos de memória e tornar o aplicativo mais eficiente. Para fazer isso, retornaríamos a função de limpeza no final do gancho.

 useEffect(() => { console.log('mounted') return () => console.log('unmounting... clean up here') })

O gancho useEffect acima será mounted em log quando o componente for montado. Desmontando… a limpeza aqui será registrada quando o componente for desmontado. Isso pode acontecer quando o componente é removido da interface do usuário.

O processo de limpeza normalmente segue o formulário abaixo.

 useEffect(() => { //The effect we intend to make effect //We then return the clean up return () => the cleanup/unsubscription })

Embora você não encontre tantos casos de uso para assinaturas useEffect , é útil ao lidar com assinaturas e cronômetros. Particularmente, ao lidar com web sockets, pode ser necessário cancelar a assinatura da rede para economizar recursos e melhorar o desempenho quando o componente for desmontado.

Buscando e recuperando dados com useEffect

Um dos casos de uso mais comuns do gancho useEffect é buscar e pré-buscar dados de uma API.

Para ilustrar isso, usaremos dados de usuário falsos que criei de JSONPlaceholder para buscar dados com o gancho useEffect .

 import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [users, setUsers] = useState([]); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/users"; useEffect(() => { const fetchUsers = async () => { const { data } = await axios.get(endPoint); setUsers(data); }; fetchUsers(); }, []); return ( <div className="App"> {users.map((user) => ( <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> ))} </div> ); }

No código acima, criamos um estado de users usando o gancho useState . Em seguida, buscamos dados de uma API usando o Axios. Este é um processo assíncrono e, portanto, usamos a função async/await, também poderíamos ter usado o ponto e depois a sintaxe. Como buscamos uma lista de usuários, simplesmente mapeamos por meio dela para exibir os dados.

Observe que passamos um parâmetro vazio para o gancho. Isso garante que ele seja chamado apenas uma vez quando o componente for montado.

Também podemos buscar novamente os dados quando algumas condições mudam. Vamos mostrar isso no código abaixo.

 import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [userIDs, setUserIDs] = useState([]); const [user, setUser] = useState({}); const [currentID, setCurrentID] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/userdata/users"; useEffect(() => { axios.get(endPoint).then(({ data }) => setUserIDs(data)); }, []); useEffect(() => { const fetchUserIDs = async () => { const { data } = await axios.get(`${endPoint}/${currentID}`}); setUser(data); }; fetchUserIDs(); }, [currentID]); const moveToNextUser = () => { setCurrentID((prevId) => (prevId < userIDs.length ? prevId + 1 : prevId)); }; const moveToPrevUser = () => { setCurrentID((prevId) => (prevId === 1 ? prevId : prevId - 1)); }; return ( <div className="App"> <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> <button onClick={moveToPrevUser}>Prev</button> <button onClick={moveToNextUser}>Next</button> </div> ); }

Aqui criamos dois ganchos useEffect . No primeiro, usamos a sintaxe dot then para obter todos os usuários da nossa API. Isso é necessário para determinar o número de usuários.

Em seguida, criamos outro gancho useEffect para obter um usuário com base no id . Este useEffect irá buscar novamente os dados sempre que o id for alterado. Para garantir isso, passamos o id na lista de dependências.

Em seguida, criamos funções para atualizar o valor do nosso id sempre que os botões são clicados. Assim que o valor do id for alterado, o useEffect será executado novamente e buscará novamente os dados.

Se quisermos, podemos até limpar ou cancelar o token baseado em promessa no Axios, podemos fazer isso com o método de limpeza discutido acima.

 useEffect(() => { const source = axios.CancelToken.source(); const fetchUsers = async () => { const { data } = await axios.get(`${endPoint}/${num}`, { cancelToken: source.token }); setUser(data); }; fetchUsers(); return () => source.cancel(); }, [num]);

Aqui, passamos o token do Axios como um segundo parâmetro para axios.get . Quando o componente é desmontado, cancelamos a assinatura chamando o método cancel do objeto de origem.

O gancho useReducer

O gancho useReducer é um gancho React muito útil que faz algo semelhante ao gancho useState . De acordo com a documentação do React, esse gancho deve ser usado para lidar com lógica mais complexa do que o gancho useState . É digno de nota que o gancho useState é implementado internamente com o gancho useReducer.

O gancho recebe um redutor como argumento e pode, opcionalmente, receber o estado inicial e uma função init como argumentos.

 const [state, dispatch] = useReducer(reducer, initialState, init)

Aqui, init é uma função e é usada sempre que queremos criar o estado inicial preguiçosamente.

Vejamos como implementar o gancho useReducer criando um aplicativo de tarefas simples, conforme mostrado na sandbox abaixo.

Todo Exemplo

Primeiro, devemos criar nosso redutor para manter os estados.

 export const ADD_TODO = "ADD_TODO"; export const REMOVE_TODO = "REMOVE_TODO"; export const COMPLETE_TODO = "COMPLETE_TODO"; const reducer = (state, action) => { switch (action.type) { case ADD_TODO: const newTodo = { id: action.id, text: action.text, completed: false }; return [...state, newTodo]; case REMOVE_TODO: return state.filter((todo) => todo.id !== action.id); case COMPLETE_TODO: const completeTodo = state.map((todo) => { if (todo.id === action.id) { return { ...todo, completed: !todo.completed }; } else { return todo; } }); return completeTodo; default: return state; } }; export default reducer;

Criamos três constantes correspondentes aos nossos tipos de ação. Poderíamos ter usado strings diretamente, mas esse método é preferível para evitar erros de digitação.

Então criamos nossa função redutora. Como no Redux , o redutor deve pegar o estado e o objeto de ação. Mas ao contrário do Redux, não precisamos inicializar nosso redutor aqui.

Além disso, para muitos casos de uso de gerenciamento de estado, um useReducer junto com o dispatch exposto via context pode permitir que um aplicativo maior dispare ações, atualize state e o escute.

Em seguida, usamos as instruções switch para verificar o tipo de ação passado pelo usuário. Se o tipo de ação for ADD_TODO , queremos passar uma nova pendência e se for REMOVE_TODO , queremos filtrar as pendências e remover aquela que corresponde ao id passado pelo usuário. Se for COMPLETE_TODO , queremos mapear as pendências e alternar aquela com o id passado pelo usuário.

Aqui está o arquivo App.js onde implementamos o reducer .

 import { useReducer, useState } from "react"; import "./styles.css"; import reducer, { ADD_TODO, REMOVE_TODO, COMPLETE_TODO } from "./reducer"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = [ { id: id, text: "First Item", completed: false } ]; //We could also pass an empty array as the initial state //const initialState = [] const [state, dispatch] = useReducer(reducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); dispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="App"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit">+</button> </form> <div className="todos"> {state.map((todo) => ( <div key={todo.id} className="todoItem"> <p className={todo.completed && "strikethrough"}>{todo.text}</p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ))} </div> </div> ); }

Aqui, criamos um formulário contendo um elemento de entrada, para coletar a entrada do usuário, e um botão para acionar a ação. Quando o formulário é enviado, despachamos uma ação do tipo ADD_TODO , passando um novo id e texto de pendências. Criamos um novo id incrementando o valor do id anterior em 1. Em seguida, limpamos a caixa de texto de entrada. Para excluir e concluir a tarefa, simplesmente despachamos as ações apropriadas. Estes já foram implementados no redutor como mostrado acima.

No entanto, a mágica acontece porque estamos usando o gancho useReducer . Este gancho aceita o redutor e o estado inicial e retorna o estado e a função de despacho. Aqui, a função dispatch serve ao mesmo propósito que a função setter para o hook useState e podemos chamá-la como quisermos em vez de dispatch .

Para exibir os itens de pendências, simplesmente mapeamos a lista de pendências retornadas em nosso objeto de estado, conforme mostrado no código acima.

Isso mostra o poder do gancho useReducer . Também poderíamos obter essa funcionalidade com o gancho useState , mas como você pode ver no exemplo acima, o gancho useReducer nos ajudou a manter as coisas mais organizadas. useReducer geralmente é benéfico quando o objeto de estado é uma estrutura complexa e é atualizado de maneiras diferentes em relação a uma simples substituição de valor. Além disso, uma vez que essas funções de atualização ficam mais complicadas, useReducer torna fácil manter toda essa complexidade em uma função redutora (que é uma função JS pura), tornando muito fácil escrever testes apenas para a função redutora.

Poderíamos também ter passado o terceiro argumento para o gancho useReducer para criar o estado inicial preguiçosamente. Isso significa que podemos calcular o estado inicial em uma função init .

Por exemplo, poderíamos criar uma função init da seguinte forma:

 const initFunc = () => [ { id: id, text: "First Item", completed: false } ]

e, em seguida, passá-lo para o nosso gancho useReducer .

 const [state, dispatch] = useReducer(reducer, initialState, initFunc)

Se fizermos isso, o initFunc substituirá o initialState que fornecemos e o estado inicial será calculado lentamente.

O gancho useContext

A API do React Context fornece uma maneira de compartilhar estados ou dados em toda a árvore de componentes do React. A API está disponível no React, como um recurso experimental, há algum tempo, mas tornou-se segura para uso no React 16.3.0. A API facilita o compartilhamento de dados entre os componentes, eliminando a perfuração de hélices.

Embora você possa aplicar o React Context a todo o seu aplicativo, também é possível aplicá-lo a parte do aplicativo.

Para usar o gancho, você precisa primeiro criar um contexto usando React.createContext e esse contexto pode ser passado para o gancho.

Para demonstrar o uso do gancho useContext , vamos criar um aplicativo simples que aumentará o tamanho da fonte em todo o nosso aplicativo.

Vamos criar nosso contexto no arquivo context.js .

 import { createContext } from "react"; //Here, we set the initial fontSize as 16. const fontSizeContext = createContext(16); export default fontSizeContext;

Aqui, criamos um contexto e passamos um valor inicial de 16 para ele e, em seguida, exportamos o contexto. Em seguida, vamos conectar nosso contexto ao nosso aplicativo.

 import FontSizeContext from "./context"; import { useState } from "react"; import PageOne from "./PageOne"; import PageTwo from "./PageTwo"; const App = () => { const [size, setSize] = useState(16); return ( <FontSizeContext.Provider value={size}> <PageOne /> <PageTwo /> <button onClick={() => setSize(size + 5)}>Increase font</button> <button onClick={() => setSize((prevSize) => Math.min(11, prevSize - 5)) } > Decrease font </button> </FontSizeContext.Provider> ); }; export default App;

No código acima, agrupamos toda a nossa árvore de componentes com FontSizeContext.Provider e passamos size para sua propriedade de valor. Aqui, size é um estado criado com o gancho useState . Isso nos permite alterar o valor prop sempre que o estado do size mudar. Ao envolver todo o componente com o Provider , podemos acessar o contexto em qualquer lugar em nosso aplicativo.

Por exemplo, acessamos o contexto em <PageOne /> e <PageTwo /> . Como resultado disso, o tamanho da fonte aumentará nesses dois componentes quando o aumentarmos no arquivo App.js Podemos aumentar ou diminuir o tamanho da fonte nos botões, conforme mostrado acima e, uma vez que fazemos isso, o tamanho da fonte muda em todo o aplicativo.

 import { useContext } from "react"; import context from "./context"; const PageOne = () => { const size = useContext(context); return <p style={{ fontSize: `${size}px` }}>Content from the first page</p>; }; export default PageOne;

Aqui, acessamos o contexto usando o gancho useContext do nosso componente PageOne . Em seguida, usamos esse contexto para definir nossa propriedade font-size. Um procedimento semelhante se aplica ao arquivo PageTwo.js .

Temas ou outras configurações de nível de aplicativo de ordem superior são bons candidatos para contextos.

Usando useContext e useReducer

Quando usado com o gancho useReducer , useContext nos permite criar nosso próprio sistema de gerenciamento de estado. Podemos criar estados globais e gerenciá-los facilmente em nosso aplicativo.

Vamos melhorar nosso aplicativo de tarefas usando a API de contexto.

Como de costume, precisamos criar um todoContext no arquivo todoContext.js .

 import { createContext } from "react"; const initialState = []; export default createContext(initialState);

Aqui criamos o contexto, passando um valor inicial de um array vazio. Em seguida, exportamos o contexto.

Vamos refatorar nosso arquivo App.js separando a lista de tarefas e os itens.

 import { useReducer, useState } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import TodoList from "./TodoList"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <div className="app"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </TodoContext.Provider> ); }

Aqui, agrupamos nosso arquivo App.js com o TodoContext.Provider e passamos os valores de retorno de nosso todoReducer para ele. Isso torna o estado do redutor e a função de dispatch acessíveis em todo o nosso aplicativo.

Em seguida, separamos a exibição de tarefas em um componente TodoList . Fizemos isso sem perfuração de prop, graças à API Context. Vamos dar uma olhada no arquivo TodoList.js .

 import React, { useContext } from "react"; import TodoContext from "./todoContext"; import Todo from "./Todo"; const TodoList = () => { const [state] = useContext(TodoContext); return ( <div className="todos"> {state.map((todo) => ( <Todo key={todo.id} todo={todo} /> ))} </div> ); }; export default TodoList;

Usando a desestruturação de array, podemos acessar o estado (deixando a função dispatch) do contexto usando o gancho useContext . Podemos então mapear o estado e exibir os itens pendentes. Ainda extraímos isso em um componente Todo . A função de mapa do ES6+ exige que passemos uma chave única e, como precisamos da tarefa específica, também a passamos ao lado.

Vamos dar uma olhada no componente Todo .

 import React, { useContext } from "react"; import TodoContext from "./todoContext"; import { REMOVE_TODO, COMPLETE_TODO } from "./todoReducer"; const Todo = ({ todo }) => { const [, dispatch] = useContext(TodoContext); const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="todoItem"> <p className={todo.completed ? "strikethrough" : "nostrikes"}> {todo.text} </p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ); }; export default Todo;

Novamente usando a desestruturação de array, acessamos a função dispatch do contexto. Isso nos permite definir a função completeTodo e removeTodo como já discutido na seção useReducer . Com a prop todo passada de todoList.js podemos exibir um item de pendências. Também podemos marcá-lo como concluído e remover a tarefa conforme julgarmos adequado.

Também é possível aninhar mais de um provedor de contexto na raiz do nosso aplicativo. Isso significa que podemos usar mais de um contexto para realizar diferentes funções em uma aplicação.

Para demonstrar isso, vamos adicionar temas ao exemplo de tarefas pendentes.

Aqui está o que vamos construir.

Novamente, temos que criar themeContext . Para fazer isso, crie um arquivo themeContext.js e adicione os seguintes códigos.

 import { createContext } from "react"; import colors from "./colors"; export default createContext(colors.light);

Aqui, criamos um contexto e passamos colors.light como valor inicial. Vamos definir as cores com esta propriedade no arquivo colors.js .

 const colors = { light: { backgroundColor: "#fff", color: "#000" }, dark: { backgroundColor: "#000", color: "#fff" } }; export default colors;

No código acima, criamos um objeto colors contendo as propriedades light e dark. Cada propriedade tem backgroundColor e objeto de color .

Em seguida, criamos o themeReducer para lidar com os estados do tema.

 import Colors from "./colors"; export const LIGHT = "LIGHT"; export const DARK = "DARK"; const themeReducer = (state, action) => { switch (action.type) { case LIGHT: return { ...Colors.light }; case DARK: return { ...Colors.dark }; default: return state; } }; export default themeReducer;

Como todos os redutores, o themeReducer pega o estado e a ação. Em seguida, ele usa a instrução switch para determinar a ação atual. Se for do tipo LIGHT , simplesmente atribuímos props Colors.light e se for do tipo DARK , exibimos props Colors.dark . Poderíamos ter feito isso facilmente com o gancho useState , mas escolhemos useReducer para direcionar o ponto para casa.

Depois de configurar o themeReducer , podemos integrá-lo em nosso arquivo App.js

 import { useReducer, useState, useCallback } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import ThemeContext from "./themeContext"; import TodoList from "./TodoList"; import themeReducer, { DARK, LIGHT } from "./themeReducer"; import Colors from "./colors"; import ThemeToggler from "./ThemeToggler"; const themeSetter = useCallback( theme => themeDispatch({type: theme}, [themeDispatch]); export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const [themeState, themeDispatch] = useReducer(themeReducer, Colors.light); const themeSetter = useCallback( (theme) => { themeDispatch({ type: theme }); }, [themeDispatch] ); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <ThemeContext.Provider value={[ themeState, themeSetter ]} > <div className="app" style={{ ...themeState }}> <ThemeToggler /> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </ThemeContext.Provider> </TodoContext.Provider> ); }

No código acima, adicionamos algumas coisas ao nosso aplicativo de tarefas já existente. Começamos importando ThemeContext , themeReducer , ThemeToggler e Colors . We created a reducer using the useReducer hook, passing the themeReducer and an initial value of Colors.light to it. This returned the themeState and themeDispatch to us.

We then nested our component with the provider function from the ThemeContext , passing the themeState and the dispatch functions to it. We also added theme styles to it by spreading out the themeStates . This works because the colors object already defined properties similar to what the JSX styles will accept.

However, the actual theme toggling happens in the ThemeToggler component. Vamos dar uma olhada nisso.

 import ThemeContext from "./themeContext"; import { useContext, useState } from "react"; import { DARK, LIGHT } from "./themeReducer"; const ThemeToggler = () => { const [showLight, setShowLight] = useState(true); const [themeState, themeSetter] = useContext(ThemeContext); const dispatchDarkTheme = () => themeSetter(DARK); const dispatchLightTheme = () => themeSetter(LIGHT); const toggleTheme = () => { showLight ? dispatchDarkTheme() : dispatchLightTheme(); setShowLight(!showLight); }; console.log(themeState); return ( <div> <button onClick={toggleTheme}> {showLight ? "Change to Dark Theme" : "Change to Light Theme"} </button> </div> ); }; export default ThemeToggler;

In this component, we used the useContext hook to retrieve the values we passed to the ThemeContext.Provider from our App.js file. As shown above, these values include the ThemeState , dispatch function for the light theme, and dispatch function for the dark theme. Thereafter, we simply called the dispatch functions to toggle the themes. We also created a state showLight to determine the current theme. This allows us to easily change the button text depending on the current theme.

The useMemo Hook

The useMemo hook is designed to memoize expensive computations. Memoization simply means caching. It caches the computation result with respect to the dependency values so that when the same values are passed, useMemo will just spit out the already computed value without recomputing it again. This can significantly improve performance when done correctly.

The hook can be used as follows:

 const memoizedResult = useMemo(() => expensiveComputation(a, b), [a, b])

Let's consider three cases of the useMemo hook.

  1. When the dependency values, a and b remain the same.
    The useMemo hook will return the already computed memoized value without recomputation.
  2. When the dependency values, a and b change.
    The hook will recompute the value.
  3. When no dependency value is passed.
    The hook will recompute the value.

Let's take a look at an example to demonstrate this concept.

In the example below, we'll be computing the PAYE and Income after PAYE of a company's employees with fake data from JSONPlaceholder.

The calculation will be based on the personal income tax calculation procedure for Nigeria providers by PricewaterhouseCoopers available here.

This is shown in the sandbox below.

First, we queried the API to get the employees' data. We also get data for each employee (with respect to their employee id).

const [employee, setEmployee] = useState({}); const [employees, setEmployees] = useState([]); const [num, setNum] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; useEffect(() => { const getEmployee = async () => { const { data } = await axios.get(`${endPoint}/${num}`); setEmployee(data); }; getEmployee(); }, [num]); useEffect(() => { axios.get(endPoint).then(({ data }) => setEmployees(data)); }, [num]);

Usamos axios e o método async/await await no primeiro useEffect e depois a sintaxe dot then no segundo. Essas duas abordagens funcionam da mesma maneira.

Em seguida, usando os dados de funcionários que obtivemos acima, vamos calcular as variáveis ​​de alívio:

 const taxVariablesCompute = useMemo(() => { const { income, noOfChildren, noOfDependentRelatives } = employee; //supposedly complex calculation //tax relief computations for relief Allowance, children relief, // relatives relief and pension relief const reliefs = reliefAllowance1 + reliefAllowance2 + childrenRelief + relativesRelief + pensionRelief; return reliefs; }, [employee]);

Este é um cálculo bastante complexo e, portanto, tivemos que envolvê-lo em um gancho useMemo para memorizá-lo ou otimizá-lo. Memorizá-lo dessa maneira garantirá que o cálculo não seja recalculado se tentarmos acessar o mesmo funcionário novamente.

Além disso, usando os valores de isenção de impostos obtidos acima, gostaríamos de calcular o PAYE e a renda após PAYE.

 const taxCalculation = useMemo(() => { const { income } = employee; let taxableIncome = income - taxVariablesCompute; let PAYE = 0; //supposedly complex calculation //computation to compute the PAYE based on the taxable income and tax endpoints const netIncome = income - PAYE; return { PAYE, netIncome }; }, [employee, taxVariablesCompute]);

Realizamos o cálculo de impostos (um cálculo bastante complexo) usando as variáveis ​​de impostos calculadas acima e depois o memorizamos com o gancho useMemo .

O código completo está disponível aqui.

Isso segue o procedimento de cálculo de imposto fornecido aqui. Primeiro computamos a desoneração considerando renda, número de filhos e número de parentes dependentes. Em seguida, multiplicamos o lucro tributável pelas alíquotas do IRPJ em etapas. Embora o cálculo em questão não seja totalmente necessário para este tutorial, ele é fornecido para nos mostrar por que o useMemo pode ser necessário. Este também é um cálculo bastante complexo e, portanto, podemos precisar memorizá-lo com useMemo , conforme mostrado acima.

Após calcular os valores, simplesmente exibimos o resultado.

Observe o seguinte sobre o gancho useMemo .

  • useMemo deve ser usado somente quando for necessário otimizar o cálculo. Em outras palavras, quando a recomputação é cara.
  • É aconselhável escrever primeiro o cálculo sem memorização e apenas memorizá-lo se estiver causando problemas de desempenho.
  • O uso desnecessário e irrelevante do gancho useMemo pode até agravar os problemas de desempenho.
  • Às vezes, muita memorização também pode causar problemas de desempenho.

O gancho useCallback

useCallback tem a mesma finalidade que useMemo , mas retorna um retorno de chamada memorizado em vez de um valor memorizado. Em outras palavras, useCallback é o mesmo que passar useMemo sem uma chamada de função.

Por exemplo, considere os seguintes códigos abaixo.

 import React, {useCallback, useMemo} from 'react' const MemoizationExample = () => { const a = 5 const b = 7 const memoResult = useMemo(() => a + b, [a, b]) const callbackResult = useCallback(a + b, [a, b]) console.log(memoResult) console.log(callbackResult) return( <div> ... </div> ) } export default MemoizationExample

No exemplo acima, tanto memoResult quanto callbackResult fornecerão o mesmo valor de 12 . Aqui, useCallback retornará um valor memorizado. No entanto, também podemos fazer com que ele retorne um retorno de chamada memorizado passando-o como uma função.

O useCallback abaixo retornará um retorno de chamada memorizado.

 ... const callbackResult = useCallback(() => a + b, [a, b]) ...

Podemos então acionar o retorno de chamada quando uma ação é executada ou em um gancho useEffect .

 import {useCallback, useEffect} from 'react' const memoizationExample = () => { const a = 5 const b = 7 const callbackResult = useCallback(() => a + b, [a, b]) useEffect(() => { const callback = callbackResult() console.log(callback) }) return ( <div> <button onClick= {() => console.log(callbackResult())}> Trigger Callback </button> </div> ) } export default memoizationExample

No código acima, definimos uma função de retorno de chamada usando o gancho useCallback . Em seguida, chamamos o retorno de chamada em um gancho useEffect quando o componente é montado e também quando um botão é clicado.

Tanto o useEffect quanto o clique do botão produzem o mesmo resultado.

Observe que os conceitos, o que fazer e o que não fazer que se aplicam ao gancho useMemo também se aplicam ao gancho useCallback . Podemos recriar o exemplo useMemo com useCallback .

O gancho useRef

useRef retorna um objeto que pode persistir em um aplicativo. O gancho tem apenas uma propriedade, current , e podemos facilmente passar um argumento para ele.

Ele serve ao mesmo propósito que um createRef usado em componentes baseados em classe. Podemos criar uma referência com este gancho da seguinte forma:

 const newRef = useRef('')

Aqui criamos um novo ref chamado newRef e passamos uma string vazia para ele.

Este gancho é usado principalmente para dois propósitos:

  1. Acessar ou manipular o DOM e
  2. Armazenando estados mutáveis ​​— isso é útil quando não queremos que o componente seja renderizado novamente quando um valor for alterado.

Manipulando o DOM

Quando passado para um elemento DOM, o objeto ref aponta para esse elemento e pode ser usado para acessar seus atributos e propriedades DOM.

Aqui está um exemplo muito simples para demonstrar este conceito.

 import React, {useRef, useEffect} from 'react' const RefExample = () => { const headingRef = useRef('') console.log(headingRef) return( <div> <h1 className='topheading' ref={headingRef}>This is a h1 element</h1> </div> ) } export default RefExample

No exemplo acima, definimos headingRef usando o gancho useRef passando uma string vazia. Em seguida, definimos o ref na tag h1 passando ref = {headingRef} . Ao definir esta ref, pedimos ao headingRef para apontar para o nosso elemento h1 . Isso significa que podemos acessar as propriedades do nosso elemento h1 a partir da referência.

Para ver isso, se verificarmos o valor de console.log(headingRef) , obteremos {current: HTMLHeadingElement} ou {current: h1} e poderemos avaliar todas as propriedades ou atributos do elemento. Algo semelhante se aplica a qualquer outro elemento HTML.

Por exemplo, poderíamos deixar o texto em itálico quando o componente for montado.

 useEffect(() => { headingRef.current.style.font; }, []);

Podemos até mudar o texto para outra coisa.

 ... headingRef.current.innerHTML = "A Changed H1 Element"; ...

Também podemos alterar a cor de fundo do contêiner pai.

 ... headingRef.current.parentNode.style.backgroundColor = "red"; ...

Qualquer tipo de manipulação do DOM pode ser feito aqui. Observe que headingRef.current pode ser lido da mesma forma que document.querySelector('.topheading') .

Um caso de uso interessante do gancho useRef na manipulação do elemento DOM é focar o cursor no elemento de entrada. Vamos rapidamente percorrê-lo.

 import {useRef, useEffect} from 'react' const inputRefExample = () => { const inputRef = useRef(null) useEffect(() => { inputRef.current.focus() }, []) return( <div> <input ref={inputRef} /> <button onClick = {() => inputRef.current.focus()}>Focus on Input </button> </div> ) } export default inputRefExample

No código acima, criamos inputRef usando o gancho useRef e pedimos para apontar para o elemento input. Em seguida, fizemos o foco do cursor na referência de entrada quando o componente é carregado e quando o botão é clicado usando inputRef.current.focus() . Isso é possível porque focus() é um atributo de elementos de entrada e, portanto, o ref poderá avaliar os métodos.

As referências criadas em um componente pai podem ser avaliadas no componente filho encaminhando-o usando React.forwardRef() . Vamos dar uma olhada nisso.

Vamos primeiro criar outro componente NewInput.js e adicionar os seguintes códigos a ele.

 import { useRef, forwardRef } from "react"; const NewInput = forwardRef((props, ref) => { return <input placeholder={props.val} ref={ref} />; }); export default NewInput;

Este componente aceita props e ref . Passamos o ref para sua prop ref e props.val para sua prop placeholder. Componentes regulares do React não recebem um atributo ref . Este atributo está disponível apenas quando o envolvemos com React.forwardRef como mostrado acima.

Podemos então chamar isso facilmente no componente pai.

 ... <NewInput val="Just an example" ref={inputRef} /> ...

Armazenando os estados mutáveis

Refs não são usados ​​apenas para manipular elementos DOM, eles também podem ser usados ​​para armazenar valores mutáveis ​​sem renderizar novamente o componente inteiro.

O exemplo a seguir detectará o número de vezes que um botão é clicado sem renderizar novamente o componente.

 import { useRef } from "react"; export default function App() { const countRef = useRef(0); const increment = () => { countRef.current++; console.log(countRef); }; return ( <div className="App"> <button onClick={increment}>Increment </button> </div> ); }

No código acima, incrementamos o countRef quando o botão é clicado e o registramos no console. Embora o valor seja incrementado conforme mostrado no console, não poderemos ver nenhuma alteração se tentarmos avaliá-lo diretamente em nosso componente. Ele só será atualizado no componente quando for renderizado novamente.

Observe que enquanto useState é assíncrono, useRef é síncrono. Em outras palavras, o valor fica disponível imediatamente após ser atualizado.

O gancho useLayoutEffect

Assim como o hook useEffect , useLayoutEffect é chamado depois que o componente é montado e renderizado. Esse gancho é acionado após a mutação do DOM e o faz de forma síncrona. Além de ser chamado de forma síncrona após a mutação DOM, useLayoutEffect faz a mesma coisa que useEffect .

useLayoutEffect deve ser usado apenas para realizar a mutação do DOM ou medição relacionada ao DOM, caso contrário, você deve usar o gancho useEffect . Usar o gancho useEffect para funções de mutação do DOM pode causar alguns problemas de desempenho, como oscilação, mas useLayoutEffect os trata perfeitamente enquanto é executado após a ocorrência das mutações.

Vamos dar uma olhada em alguns exemplos para demonstrar esse conceito.

  1. Obteremos a largura e a altura da janela ao redimensionar.
 import {useState, useLayoutEffect} from 'react' const ResizeExample = () =>{ const [windowSize, setWindowSize] = useState({width: 0, height: 0}) useLayoutEffect(() => { const resizeWindow = () => setWindowSize({ width: window.innerWidth, height: window.innerHeight }) window.addEventListener('resize', resizeWindow) return () => window.removeEventListener('resize', resizeWindow) }, []) return ( <div> <p>width: {windowSize.width}</p> <p>height: {windowSize.height}</p> </div> ) } export default ResizeExample

No código acima, criamos um estado windowSize com propriedades de largura e altura. Em seguida, definimos o estado para a largura e altura da janela atual, respectivamente, quando a janela é redimensionada. Também limpamos o código quando ele é desmontado. O processo de limpeza é essencial em useLayoutEffect para limpar a manipulação do DOM e melhorar a eficiência.

  1. Vamos desfocar um texto com useLayoutEffect .
 import { useRef, useState, useLayoutEffect } from "react"; export default function App() { const paragraphRef = useRef(""); useLayoutEffect(() => { const { current } = paragraphRef; const blurredEffect = () => { current.style.color = "transparent"; current.style.textShadow = "0 0 5px rgba(0,0,0,0.5)"; }; current.addEventListener("click", blurredEffect); return () => current.removeEventListener("click", blurredEffect); }, []); return ( <div className="App"> <p ref={paragraphRef}>This is the text to blur</p> </div> ); }

Usamos useRef e useLayoutEffect juntos no código acima. Primeiro criamos um ref, paragraphRef para apontar para o nosso parágrafo. Em seguida, criamos um ouvinte de evento ao clicar para monitorar quando o parágrafo é clicado e, em seguida, desfoca-lo usando as propriedades de estilo que definimos. Por fim, limpamos o ouvinte de eventos usando removeEventListener .

Os ganchos useDispatch e useSelector

useDispatch é um gancho do Redux para despachar (disparar) ações em um aplicativo. Ele recebe um objeto de ação como argumento e invoca a ação. useDispatch é a equivalência do gancho para mapDispatchToProps .

Por outro lado, useSelector é um gancho do Redux para avaliar os estados do Redux. É preciso uma função para selecionar o redutor Redux exato da loja e, em seguida, retornar os estados correspondentes.

Uma vez que nossa loja Redux esteja conectada a um aplicativo React por meio do provedor Redux, podemos invocar as ações com useDispatch e acessar os estados com useSelector . Cada ação e estado do Redux pode ser avaliado com esses dois ganchos.

Observe que esses estados são fornecidos com o React Redux (um pacote que facilita a avaliação do armazenamento Redux em um aplicativo React). Eles não estão disponíveis na biblioteca principal do Redux.

Esses ganchos são muito simples de usar. Primeiro, temos que declarar a função dispatch e depois acioná-la.

 import {useDispatch, useSelector} from 'react-redux' import {useEffect} from 'react' const myaction from '...' const ReduxHooksExample = () =>{ const dispatch = useDispatch() useEffect(() => { dispatch(myaction()); //alternatively, we can do this dispatch({type: 'MY_ACTION_TYPE'}) }, []) const mystate = useSelector(state => state.myReducerstate) return( ... ) } export default ReduxHooksExample

No código acima, importamos useDispatch e useSelector de react react-redux . Então, em um gancho useEffect , despachamos a ação. Podemos definir a ação em outro arquivo e chamá-la aqui ou podemos defini-la diretamente como mostrado na chamada useEffect .

Assim que despacharmos as ações, nossos estados estarão disponíveis. Podemos então recuperar o estado usando o gancho useSelector como mostrado. Os estados podem ser usados ​​da mesma forma que usaríamos os estados do gancho useState .

Vamos dar uma olhada em um exemplo para demonstrar esses dois ganchos.

Para demonstrar esse conceito, temos que criar um repositório Redux, um redutor e ações. Para simplificar as coisas aqui, usaremos a biblioteca Redux Toolkit com nosso banco de dados falso do JSONPlaceholder.

Precisamos instalar os seguintes pacotes para começar. Execute os seguintes comandos bash.

 npm i redux @reduxjs/toolkit react-redux axios

Primeiro, vamos criar o employeesSlice.js para lidar com o redutor e a ação da API de nossos funcionários.

 import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import axios from "axios"; const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; export const fetchEmployees = createAsyncThunk("employees/fetchAll", async () => { const { data } = await axios.get(endPoint); return data; }); const employeesSlice = createSlice({ name: "employees", initialState: { employees: [], loading: false, error: "" }, reducers: {}, extraReducers: { [fetchEmployees.pending]: (state, action) => { state.status = "loading"; }, [fetchEmployees.fulfilled]: (state, action) => { state.status = "success"; state.employees = action.payload; }, [fetchEmployees.rejected]: (state, action) => { state.status = "error"; state.error = action.error.message; } } }); export default employeesSlice.reducer;

Esta é a configuração padrão para o kit de ferramentas Redux. Usamos o createAsyncThunk para acessar o middleware Thunk para realizar ações assíncronas. Isso nos permitiu buscar a lista de funcionários da API. Em seguida, criamos o employeesSlice e retornamos, “carregando”, “erro”, e os dados dos funcionários dependendo dos tipos de ação.

O kit de ferramentas Redux também facilita a configuração da loja. Aqui é a loja.

 import { configureStore } from "@reduxjs/toolkit"; import { combineReducers } from "redux"; import employeesReducer from "./employeesSlice"; const reducer = combineReducers({ employees: employeesReducer }); export default configureStore({ reducer });;

Aqui, usamos combineReducers para agrupar os redutores e a função configureStore fornecida pelo kit de ferramentas Redux para configurar a loja.

Vamos continuar a usar isso em nosso aplicativo.

Primeiro, precisamos conectar o Redux ao nosso aplicativo React. Idealmente, isso deve ser feito na raiz do nosso aplicativo. Eu gosto de fazer isso no arquivo index.js .

 import React, { StrictMode } from "react"; import ReactDOM from "react-dom"; import store from "./redux/store"; import { Provider } from "react-redux"; import App from "./App"; const rootElement = document.getElementById("root"); ReactDOM.render( <Provider store={store}> <StrictMode> <App /> </StrictMode> </Provider>, rootElement );

Aqui, importei a loja que criei acima e também o Provider do react-redux .

Então, eu envolvi todo o aplicativo com a função Provider , passando a loja para ele. Isso torna a loja acessível em todo o nosso aplicativo.

Podemos então continuar a usar os ganchos useDispatch e useSelector para buscar os dados.

Vamos fazer isso em nosso arquivo App.js

 import { useDispatch, useSelector } from "react-redux"; import { fetchEmployees } from "./redux/employeesSlice"; import { useEffect } from "react"; export default function App() { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchEmployees()); }, [dispatch]); const employeesState = useSelector((state) => state.employees); const { employees, loading, error } = employeesState; return ( <div className="App"> {loading ? ( "Loading..." ) : error ? ( <div>{error}</div> ) : ( <> <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <h3>{`${employee.firstName} ${employee.lastName}`}</h3> </div> ))} </> )} </div> ); }

No código acima, usamos o gancho useDispatch para invocar a ação fetchEmployees criada no arquivo employeesSlice.js . Isso faz com que o estado dos funcionários esteja disponível em nosso aplicativo. Em seguida, usamos o gancho useSelector para obter os estados. Em seguida, exibimos os resultados mapeando os employees .

O gancho useHistory

A navegação é muito importante em uma aplicação React. Embora você possa conseguir isso de duas maneiras, o React Router fornece uma maneira simples, eficiente e popular de obter roteamento dinâmico em um aplicativo React. Além disso, o React Router fornece alguns ganchos para avaliar o estado do roteador e executar a navegação no navegador, mas para usá-los, você precisa primeiro configurar seu aplicativo corretamente.

Para usar qualquer hook do React Router, devemos primeiro envolver nosso aplicativo com BrowserRouter . Podemos então aninhar as rotas com Switch e Route .

Mas primeiro, temos que instalar o pacote executando os seguintes comandos.

 npm install react-router-dom

Então, precisamos configurar nosso aplicativo da seguinte forma. Eu gosto de fazer isso no meu arquivo App.js

 import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import Employees from "./components/Employees"; export default function App() { return ( <div className="App"> <Router> <Switch> <Route path='/'> <Employees /> </Route> ... </Switch> </Router> </div> ); }

Poderíamos ter tantas Rotas quanto possível dependendo do número de componentes que desejamos renderizar. Aqui, renderizamos apenas o componente Employees . O atributo path informa ao React Router DOM o caminho do componente e pode ser avaliado com query string ou vários outros métodos.

A ordem importa aqui. A rota raiz deve ser colocada abaixo da rota filha e assim por diante. Para substituir essa ordem, você precisa incluir a palavra-chave exact na rota raiz.

 <Route path='/' exact > <Employees /> </Route>

Agora que configuramos o roteador, podemos usar o hook useHistory e outros hooks do React Router em nosso aplicativo.

Para usar o gancho useHistory , precisamos primeiro declará-lo da seguinte maneira.

 import {useHistory} from 'history' import {useHistory} from 'react-router-dom' const Employees = () =>{ const history = useHistory() ... }

Se registrarmos o histórico no console, veremos várias propriedades associadas a ele. Estes incluem block , createHref , go , goBack , goForward , length , listen , location , push , replace . Embora todas essas propriedades sejam úteis, você provavelmente usará history.push e history.replace com mais frequência do que outras propriedades.

Vamos usar esta propriedade para passar de uma página para outra.

Supondo que queremos buscar dados sobre um determinado funcionário quando clicamos em seus nomes. Podemos usar o gancho useHistory para navegar até a nova página onde as informações do funcionário serão exibidas.

 function moveToPage = (id) =>{ history.push(`/employees/${id}`) }

Podemos implementar isso em nosso arquivo Employee.js adicionando o seguinte.

 import { useEffect } from "react"; import { Link, useHistory, useLocation } from "react-router-dom"; export default function Employees() { const history = useHistory(); function pushToPage = (id) => { history.push(`/employees/${id}`) } ... return ( <div> ... <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <span>{`${employee.firstName} ${employee.lastName} `}</span> <button onClick={pushToPage(employee.id)}> » </button> </div> ))} </div> ); }

Na função pushToPage , usamos o history do gancho useHistory para navegar até a página do funcionário e passar o ID do funcionário ao lado.

O gancho useLocation

Este gancho também é fornecido com o React Router DOM. É um gancho muito popular usado para trabalhar com o parâmetro de string de consulta. Este gancho é semelhante ao window.location no navegador.

 import {useLocation} from 'react' const LocationExample = () =>{ const location = useLocation() return ( ... ) } export default LocationExample

O gancho useLocation retorna o pathname , o parâmetro de search , o hash e state . Os parâmetros mais usados ​​incluem o nome do pathname e a search , mas você também pode usar hash e state muito em seu aplicativo.

A propriedade location pathname retornará o caminho que definimos em nossa configuração de Route . Enquanto a search retornará o parâmetro de pesquisa da consulta, se houver. Por exemplo, se passarmos 'http://mywebsite.com/employee/?id=1' para nossa consulta, o nome do pathname seria /employee e a search seria ?id=1 .

Podemos então recuperar os vários parâmetros de pesquisa usando pacotes como query-string ou codificando-os.

O gancho useParams

Se configurarmos nossa Rota com um parâmetro de URL em seu atributo path, podemos avaliar esses parâmetros como pares de chave/valor com o gancho useParams .

Por exemplo, vamos supor que temos a seguinte Route.

 <Route path='/employees/:id' > <Employees /> </Route>

A Rota estará esperando um id dinâmico no lugar de :id .

Com o hook useParams , podemos avaliar o id passado pelo usuário, se houver.

Por exemplo, supondo que o usuário passe o seguinte na função com history.push ,

 function goToPage = () => { history.push(`/employee/3`) }

Podemos usar o gancho useParams para acessar esse parâmetro de URL da seguinte maneira.

 import {useParams} from 'react-router-dom' const ParamsExample = () =>{ const params = useParams() console.log(params) return( <div> ... </div> ) } export default ParamsExample

Se registrarmos params no console, obteremos o seguinte objeto {id: "3"} .

O gancho useRouteMatch

Este gancho fornece acesso ao objeto de correspondência. Ele retorna a correspondência mais próxima de um componente se nenhum argumento for fornecido a ele.

O objeto de correspondência retorna vários parâmetros, incluindo o path (o mesmo que o caminho especificado em Route), a URL , o objeto params e isExact .

Por exemplo, podemos usar useRouteMatch para retornar componentes com base na rota.

 import { useRouteMatch } from "react-router-dom"; import Employees from "..."; import Admin from "..." const CustomRoute = () => { const match = useRouteMatch("/employees/:id"); return match ? ( <Employee /> ) : ( <Admin /> ); }; export default CustomRoute;

No código acima, definimos o caminho de uma rota com useRouteMatch e renderizamos o componente <Employee /> ou <Admin /> dependendo da rota selecionada pelo usuário.

Para que isso funcione, ainda precisamos adicionar a rota ao nosso arquivo App.js

 ... <Route> <CustomRoute /> </Route> ...

Construindo um gancho personalizado

De acordo com a documentação do React, construir um hook customizado nos permite extrair uma lógica em uma função reutilizável. No entanto, você precisa ter certeza de que todas as regras que se aplicam aos hooks do React se aplicam ao seu hook customizado. Verifique as regras do hook React na parte superior deste tutorial e certifique-se de que seu hook customizado esteja em conformidade com cada uma delas.

Ganchos personalizados nos permitem escrever funções uma vez e reutilizá-las sempre que necessário e, portanto, obedecendo ao princípio DRY.

Por exemplo, poderíamos criar um gancho personalizado para obter a posição de rolagem em nossa página da seguinte maneira.

 import { useLayoutEffect, useState } from "react"; export const useScrollPos = () => { const [scrollPos, setScrollPos] = useState({ x: 0, y: 0 }); useLayoutEffect(() => { const getScrollPos = () => setScrollPos({ x: window.pageXOffset, y: window.pageYOffset }); window.addEventListener("scroll", getScrollPos); return () => window.removeEventListener("scroll", getScrollPos); }, []); return scrollPos; };

Aqui, definimos um gancho personalizado para determinar a posição de rolagem em uma página. Para conseguir isso, primeiro criamos um estado, scrollPos , para armazenar a posição de rolagem. Como isso modificará o DOM, precisamos usar useLayoutEffect em vez de useEffect . Adicionamos um ouvinte de eventos de rolagem para capturar as posições de rolagem x e y e, em seguida, limpamos o ouvinte de eventos. Finalmente, voltamos para a posição de rolagem.

Podemos usar esse gancho personalizado em qualquer lugar em nosso aplicativo chamando-o e usando-o da mesma forma que usaríamos qualquer outro estado.

 import {useScrollPos} from './Scroll' const App = () =>{ const scrollPos = useScrollPos() console.log(scrollPos.x, scrollPos.y) return ( ... ) } export default App

Aqui, importamos o hook customizado useScrollPos que criamos acima. Em seguida, inicializamos e registramos o valor em nosso console. Se rolarmos a página, o gancho nos mostrará a posição de rolagem em cada etapa da rolagem.

Podemos criar ganchos personalizados para fazer praticamente qualquer coisa que possamos imaginar em nosso aplicativo. Como você pode ver, simplesmente precisamos usar o hook React embutido para executar algumas funções. Também podemos usar bibliotecas de terceiros para criar ganchos personalizados, mas se fizermos isso, teremos que instalar essa biblioteca para poder usar o gancho.

Conclusão

Neste tutorial, demos uma boa olhada em alguns ganchos úteis do React que você usará na maioria de seus aplicativos. Examinamos o que eles apresentam e como usá-los em sua aplicação. Também analisamos vários exemplos de código para ajudá-lo a entender esses ganchos e aplicá-los ao seu aplicativo.

Eu encorajo você a experimentar esses ganchos em seu próprio aplicativo para entendê-los mais.

Recursos dos documentos do React

  • Perguntas frequentes sobre ganchos
  • Kit de Ferramentas Redux
  • Usando o gancho de estado
  • Usando o gancho de efeito
  • Referência da API Hooks
  • Ganchos do React Redux
  • Ganchos do roteador React