Este artigo explica os Ganchos React useState e useRef . Você aprenderá seu uso básico e conhecerá os diferentes casos de uso para os dois ganchos.

Você pode encontrar os exemplos como parte de um CodeSandbox . Para ver os diferentes exemplos em ação, basta adaptar a seguinte linha em App.js:

 exportar AppDemo6 padrão;//mude para AppDemo  

Compreendendo o useState Hook

O gancho useState permite o desenvolvimento do estado do componente para componentes funcionais. Antes do React 16.8, o estado local para um componente só era possível com componentes baseados em classe.

Dê uma olhada no código a seguir.

 import {useState} de"react";
function AppDemo1 () { const stateWithUpdater=useState (true); const darkMode=stateWithUpdater [0]; const darkModeUpdater=stateWithUpdater [1]; Retorna ( 

{darkMode?"modo escuro ativado":"modo escuro desativado"}

); }

O gancho useState retorna uma matriz com dois itens. No exemplo, implementamos um estado de componente booleano e inicializamos nosso Hook com true .

Este único argumento de useState é considerado apenas durante o ciclo de renderização inicial. Se precisar de um valor inicial complexo de calcular, no entanto, você pode passar uma função de retorno de chamada para fins de otimização de desempenho.

O primeiro item da matriz representa o estado real e o segundo item constitui a função de atualização de estado. O manipulador onClick demonstra como usar a função de atualização ( darkModeUpdate ) para alterar a variável de estado ( darkMode ). É importante atualizar seu estado exatamente assim. O código a seguir é ilegal:

 darkMode=true; 

Se você tem alguma experiência com o gancho useState , pode estar se perguntando sobre a sintaxe do meu exemplo. O uso padrão é utilizar os itens de array retornados com a ajuda de desestruturação de array .

 const [darkMode, setDarkMode]=useState (true); 

Como um lembrete, é crucial seguir as regras dos Ganchos ao usar qualquer Gancho, não apenas useState ou useRef :

  • Os ganchos só devem ser chamados a partir do nível superior de sua função React
  • Os ganchos não devem ser chamados de código aninhado (por exemplo, loops, condições)
  • Ganchos também podem ser chamados no nível superior de Ganchos personalizados

Agora que cobrimos o básico, vamos examinar todos os aspectos do Gancho com o código de exemplo a seguir.

 import {useState} de"react";
import"./styles.css";
function AppDemo2 () { console.log ("renderizar aplicativo"); const [darkMode, setDarkMode]=useState (false); Retorna ( 

O gancho useState

Clique no botão para alternar o estado

{ setDarkMode (! darkMode); }} > alternar modo escuro
); }

Se darkMode for definido como true , uma classe CSS adicional ( dark-mode ) será adicionada a className , e as cores do fundo e do texto são invertidas. Como você pode ver na saída do console na gravação, toda vez que o estado muda, o componente correspondente é renderizado novamente.

Componente de aplicativo renderizado novamente a cada mudança de estado
Cada mudança de estado renderiza novamente o componente App .

React DevTools é especialmente útil aqui para destacar as atualizações visualmente quando os componentes são renderizados. Na última gravação, você pode ver a borda piscando ao redor do componente que o notifica sobre outro ciclo de renderização do componente.

Habilitando a opção React DevTools para realçar reprocessadores
Opção para realçar visualmente novas renderizações.

No próximo exemplo, os cabeçalhos são extraídos em um componente React separado ( Descrição ).

 import {useState} de"react";
import"./styles.css";
function AppDemo3 () { console.log ("renderizar aplicativo"); const [darkMode, setDarkMode]=useState (false); Retorna ( 
{ setDarkMode (! darkMode); }} > alternar modo escuro
); } Descrição const=()=> { console.log ("render Descrição"); Retorna ( <>

O gancho useState

Clique no botão para alternar o estado

); };

O componente App é renderizado sempre que o usuário clica no botão porque o manipulador de clique correspondente atualiza a variável de estado darkMode . Além disso, o componente filho Description também é renderizado.

App e os componentes filhos são renderizados novamente em cada mudança de estado
Cada mudança de estado é renderizada novamente o App e os componentes filhos.

O diagrama a seguir ilustra que uma mudança de estado causa um ciclo de renderização.

Diagrama do React Hooks Lifecycle
Uma atualização de estado renderiza novamente o componente correspondente.

Por que é importante entender o ciclo de vida do React Hooks? Por outro lado, o estado é preservado sobre a renderização, desde que você não atualize o estado por meio da função atualizador, que por si só aciona um ciclo de renderização renovado.

Usando o gancho useState com useEffect

Outro conceito importante a ser entendido é o gancho useEffect , que você provavelmente tem que usar em seu aplicativo para invocar código assíncrono (por exemplo, para buscar dados). Como você pode ver no diagrama anterior, os ganchos useState e useEffect são fortemente acoplados porque as mudanças de estado podem invocar efeitos.

Vejamos o exemplo a seguir. Introduzimos duas variáveis ​​de estado adicionais: carregando e lang . O efeito é invocado sempre que o prop url muda. Ele busca uma string de idioma ( en ou de ) e atualiza o estado com a função de atualização setLang .

Dependendo do idioma, uma string em inglês ou alemão dentro do título é renderizada. Além disso, durante o processo de busca, um estado carregando é definido e, dependendo do valor ( true ou false ), um indicador de carregamento é renderizado em vez do título.

 import {useEffect, useState} de"react";
import"./styles.css"; function App4 ({url}) { console.log ("renderizar aplicativo"); const [carregando, setLoading]=useState (true); const [lang, setLang]=useState ("de"); const [darkMode, setDarkMode]=useState (false); useEffect (()=> { console.log ("useEffect"); const fetchData=função assíncrona () { tentar { setLoading (true); resposta const=espera axios.get (url); if (response.status===200) { const {idioma}=resposta.data; setLang (idioma); } } catch (erro) { erro de lançamento; } finalmente { setLoading (false); } }; fetchData (); }, [url]); Retorna ( 
{carregando ? (
Carregando...
): ( <>

{lang==="en" ?"O gancho useState é incrível" :"Der useState Hook ist toll"}

{ setDarkMode (! darkMode); }} > alternar modo escuro )}
); }
Definição de carregamento e estado Lang dentro useEffect
Definir o carregamento e o estado do idioma dentro de useEffect.

Suponhamos que queremos alternar para o modo escuro sempre que buscarmos o idioma atual. Adicionamos uma chamada ao atualizador setDarkMode depois de atualizar o idioma. Além disso, precisamos adicionar o estado darkMode como uma dependência ao array de dependência do efeito.

Por que isso deve ser feito vai além do escopo deste artigo, mas você pode ler sobre o useEffect Hook em grande detalhe em meu post anterior.

 import {useEffect, useState} de"react";
import"./styles.css";
function AppDemo5 ({url}) { console.log ("renderizar aplicativo"); const [carregando, setLoading]=useState (true); const [lang, setLang]=useState ("de"); const [darkMode, setDarkMode]=useState (false); useEffect (()=> { console.log ("useEffect"); const fetchData=função assíncrona () { tentar { setLoading (true); resposta const=espera axios.get (url); if (response.status===200) { const {idioma}=resposta.data; setLang (idioma); setDarkMode (! darkMode); } } catch (erro) { erro de lançamento; } finalmente { setLoading (false); } }; fetchData (); }, [url, darkMode]); Retorna ( 
{carregando ? (
Carregando...
): ( <>

{lang==="en" ?"O gancho useState é incrível" :"Der useState Hook ist toll"}

{ setDarkMode (! darkMode); }} > alternar modo escuro )}
); }

Infelizmente, causamos um loop infinito.

Uso incorreto de useEffect causa um loop infinito de renderizações
O uso incorreto do estado em combinação com useEffect causa um loop infinito.

Por que isso? Como adicionamos darkMode à matriz de dependência do efeito e atualizamos esse estado exato dentro do efeito, o efeito é invocado novamente, atualiza o estado novamente e isso continua indefinidamente.

Mas há uma saída! Podemos evitar o darkMode como a dependência do efeito calculando o novo estado a partir do anterior estado . Chamamos o atualizador setDarkMode de maneira diferente, passando uma função que tem o estado anterior como argumento.

A implementação revisada de useEffect tem a seguinte aparência:

 useEffect (()=> { console.log ("useEffect"); const fetchData=função assíncrona () { tentar { setLoading (true); resposta const=espera axios.get (url); if (response.status===200) { const {idioma}=resposta.data; setLang (idioma); setDarkMode ((anterior)=>! anterior);//sem acesso ao estado darkMode } } catch (erro) { erro de lançamento; } finalmente { setLoading (false); } }; fetchData (); }, [url]);//sem dependência darkMode 

Diferenças de componentes baseados em classe

Se você usa o React há muito tempo ou atualmente está trabalhando em um código legado, conhece os componentes baseados em classes. Com componentes baseados em classe, você tem um único objeto que representa o estado do componente. Para atualizar uma fatia do estado geral, você pode aproveitar o [setState] genérico ( https://reactjs.org/docs/state-and-lifecycle.html ) método.

Imagine que queremos apenas atualizar a variável de estado darkMode . Em seguida, você pode simplesmente colocar a propriedade atualizada no objeto; o resto do estado permanece inalterado.

 this.setState ({darkMode: false}); 

Com componentes funcionais, no entanto, a maneira preferida é usar variáveis ​​de estado atômicas que podem ser atualizadas individualmente. Caso contrário, você pode rapidamente se encontrar no vale das lágrimas.

Comparado com AppDemo6 , o seguinte componente ( AppDemo7 ) foi refatorado apenas em relação ao gerenciamento de estado. Em vez de três variáveis ​​de estado atômicas com tipos de dados primitivos, usamos um objeto de estado ( estado ).

 import {useEffect, useState} de"react";
import"./styles.css";
function AppDemo7 ({url}) { const initialState={ carregando: verdadeiro, lang:"de", darkMode: true }; const [estado, setState]=useState (initialState); console.log ("renderizar aplicativo", estado); useEffect (()=> { console.log ("useEffect"); const fetchData=função assíncrona () { tentar { setState ((anterior)=> ({ carregando: verdadeiro, lang: prev.lang, darkMode: prev.darkMode })); resposta const=espera axios.get (url); if (response.status===200) { const {idioma}=resposta.data; setState ((anterior)=> ({ lang: idioma, darkMode:! prev.darkMode, carregando: prev.loading })); } } catch (erro) { erro de lançamento; } finalmente { setState ((anterior)=> ({ carregando: falso, lang: prev.lang, darkMode: prev.darkMode })); } }; fetchData (); }, [url]); Retorna ( 
{state.loading? (
Carregando...
): ( <>

{state.lang==="en" ?"O gancho useState é incrível" :"Der useState Hook ist toll"}

{ setState ((anterior)=> ({ darkMode:! prev.darkMode, //lang: prev.lang, carregando: prev.loading })); }} > alternar modo escuro )}
); }

Como você pode ver, o código é confuso e difícil de manter. Também inclui um bug ilustrado com uma propriedade comentada no manipulador onClick . Quando o usuário clica no botão, o estado geral não é calculado corretamente.

Nesse caso, a propriedade lang não está presente. Isso leva a um bug que faz com que o texto seja renderizado em alemão, já que state.lang é undefined . Espero ter mostrado definitivamente que esta é uma má ideia. A propósito, a equipe React não recomenda isso qualquer um .

Compreendendo o useRef Hook

O gancho useRef é semelhante ao useState , mas diferente 😀. Antes de esclarecer isso, explicarei seu uso básico.

 import {useRef} de'react';
const AppDemo8=()=> { const ref1=useRef (); const ref2=useRef (2021); console.log ("render"); console.log (ref1, ref2); Retorna ( 

{ref1.current}

{ref2.current}

); };

O resultado não é espetacular, mas mostra o ponto crucial.

Os valores useRef são armazenados na propriedade atual
Os valores são armazenados no propriedade atual.

Inicializamos duas referências (também conhecidas como refs) chamando. A chamada de Hook retorna um objeto que possui uma propriedade current , que armazena o valor real. Se você passar um argumento initialValue para useRef (initialValue) , esse valor será armazenado em current .

Essa é a razão pela qual a primeira saída console.log armazena undefined : porque invocamos o Gancho sem nenhum argumento. Não se preocupe, podemos atribuir valores mais tarde.

Para acessar o valor de um ref, você precisa acessar sua propriedade current , como fizemos na parte JSX. refs estão diretamente disponíveis na renderização inicial logo após terem sido definidos.

Mas por que precisamos de useRef ? Por que não usar variáveis ​​ let comuns em vez disso ? Controle-se-voltaremos a isso.

Casos de uso comuns para useRef

Vamos dar uma olhada no exemplo a seguir.

 import {useRef} de"react";
import"./styles.css";
const AppDemo9=()=> { const countRef=useRef (0); console.log ("render"); Retorna ( 

contagem: {countRef.current}

{ countRef.current=countRef.current + 1; console.log (countRef.current); }} > aumentar a contagem
); };

Nosso objetivo é definir um ref chamado countRef , inicializar o valor com 0 e aumentar esta variável de contador a cada clique de botão. O valor da contagem renderizada deve ser atualizado. Infelizmente, isso não funciona-até mesmo a saída do console prova que a propriedade current contém as atualizações corretas.

Contagem não é atualizada no clique do botão
Contagem não é atualizada clique no botão.

Como você pode ver na renderização de outra saída do console, nosso componente não é renderizada novamente. Poderíamos utilizar useState em vez de ter esse comportamento.

O quê? Portanto, useRef é bastante inútil? Não tão rápido-é útil em combinação com outros Ganchos que acionam reprocessadores, como useState , useReducer e useContext .

Você deve pensar em useRef como outra ferramenta em sua caixa de ferramentas e deve saber quando usá-la. Lembra do diagrama do ciclo de vida do componente visto acima? Os valores de refs persistem (especificamente a propriedade current ) ao longo dos ciclos de renderização. Não é um bug; é um recurso.

Considere situações em que você deseja atualizar os dados de um componente (ou seja, suas variáveis ​​de estado) para acionar uma renderização a fim de atualizar a IU. Você também pode ter situações em que deseja o mesmo comportamento com uma exceção: você não deseja acionar um ciclo de renderização porque isso pode levar a bugs, experiência do usuário estranha (por exemplo, oscilações) ou problemas de desempenho.

Você pode pensar em refs como variáveis ​​de instância de componentes baseados em classe Um ref é um contêiner genérico para armazenar qualquer tipo de dados, como dados primitivos ou objetos.

Tudo bem, mostraremos um exemplo útil.

 import {useState} de"react";
import"./styles.css";
const AppDemo10=()=> { const [valor, setValue]=useState (""); console.log ("render"); const handleInputChange=(e)=> { setValue (e.target.value); }; Retorna ( 
); };

Como você pode ver na gravação abaixo, este componente apenas renderiza um campo de entrada e armazena seu valor na variável de estado valor . A saída do console revela que o componente AppDemo10 é renderizado novamente a cada pressionamento de tecla.

Esse pode ser o comportamento que você deseja, por exemplo, realizar uma operação como uma pesquisa em cada caractere. Isso é chamado de componente controlado . No entanto, pode ser exatamente o oposto e as renderizações tornam-se problemáticas. Então você precisa de um componente não controlado .

Uma renderização de componente controlada em Cada pressionamento de tecla
Um componente controlado é renderizado a cada pressionamento de tecla.

Vamos reescrever o exemplo para usar um componente não controlado com useRef . Como consequência, precisamos de um botão para atualizar o estado do componente e armazenar o campo de entrada totalmente preenchido.

 import {useState, useRef} de"react";
import"./styles.css";
const AppDemo11=()=> { const [valor, setValue]=useState (""); const valueRef=useRef (); console.log ("render"); const handleClick=()=> { console.log (valorRef); setValue (valueRef.current.value); }; Retorna ( 

Valor: {value}

); };

Com esta solução, não causamos ciclos de renderização a cada pressionamento de tecla. Por outro lado, precisamos “enviar” a entrada com um botão para atualizar a variável de estado valor . As you can see from the console output, the second render first occurs on button click.

An Uncontrolled Component Does Not Trigger a Re-render
An uncontrolled component does not trigger re-renders on change.

By the way, the example above shows the second use case for refs.

With the ref property, React provides direct access to React components or HTML elements. The console output reveals that we indeed have access to the input element. The reference is stored in the current property.

This constitutes the second use case of useRef besides utilizing it as a generic container persisting data throughout the component lifecycle. If you need direct access to a DOM element, you can leverage the ref prop. The next example shows how to focus the input field after the component was initialized.

import { useEffect, useRef } from"react";
import"./styles.css";
const AppDemo12=()=> { const inputRef=useRef(); console.log("render"); useEffect(()=> { console.log("useEffect"); inputRef.current.focus(); }, []); return ( 
); };

Inside the useEffect callback, we call the native focus method.

Adding Focus to an Input Field Via refs
Focus an input field with the help of a ref.

This technique is also widely used in React projects in combination with third-party (non-React) components when you need direct access to DOM elements.

Another common use case is when you need the state value of the previous render cycle. The following example shows how to do this. Of course, you could also extract the logic into a custom usePrevious Hook.

import { useEffect, useState, useRef } from"react";
import"./styles.css";
const AppDemo13=()=> { console.log("render"); const [count, setCount]=useState(0); //Get the previous value (was passed into hook on last render) const ref=useRef(); //Store current value in ref useEffect(()=> { console.log("useEffect"); ref.current=count; }, [count]);//Only re-run if value changes return ( 

Now: {count}, before: {ref.current}

); };

After the initial render, an effect is executed that assigns the state variable count to the ref.current. Because no additional render occurs, the rendered value is undefined. A click on the button triggers a state update because of a call to setCount.

Next, the UI gets re-rendered, and the before label shows the correct value (0). After rendering, another effect is invoked. Now 1 gets assigned to our ref, and so on.

Accessing Previous State Via useRef
Access previous state with the help of useRef.

It’s important to note that all refs need to get updated either inside a useEffect callback or inside handlers. Mutating the ref during rendering, i.e., from places other than those just mentioned, might introduce bugs. The same applies to useState, too.

Why let can’t replace useRef

Now I still owe you a resolution for why a let variable does not replace the concept of a ref. The next example replaces the use of useRef with a vanilla JavaScript variable assignment from inside the useEffect Hook.

import { useEffect, useState } from"react";
import"./styles.css";
const AppDemo14=()=> { console.log("render"); const [count, setCount]=useState(0); let prevCount; useEffect(()=> { console.log("useEffect", prevCount); prevCount=count; }, [count]); return ( 

Now: {count}, before: {prevCount}

); };

However, the following recording will reveal that this does not work. The console output reinforces the problem because the assignment inside of useEffect gets overridden on every new render cycle. undefined is implicitly assigned because of let prevCount;.

A Normal Variable Assignment Cannot Replace useRef
A normal variable assignment cannot replace useRef.

Even the mighty ESLint Rules of Hooks plugin tells you that we should utilize useRef instead.

Warning From the ESLint Rules of Hooks Plugin
The ESLint plugin warns you about using variables instead of refs.

The differences between useRef and useState at a glance

The following differences have already been discussed in detail but are presented here again in a succinctly summarized form:

  • Both preserve their data during render cycles and UI updates, but only the useState Hook with its updater function causes re-renders
  • useRef returns an object with a current property holding the actual value. In contrast, useState returns an array with two elements: the first item constitutes the state, and the second item represents the state updater function
  • useRef‘s current property is mutable, but useState‘s state variable not. In contrast to the current property of useRef, you should not directly assign values to the state variable of useState. Instead, always use the updater function (i.e., the second array item). As the React team recommends in the documentation for setState in class-based components (but still true for function components), treat state like an immutable variable
  • useState and useRef can be considered data Hooks, but only useRef can be used in yet another field of application: to gain direct access to React components or DOM elements

Conclusão

This article addresses the useState and useRef Hooks. It should be clear at this point that there is no such thing as a good or a bad Hook. You need both Hooks for your React applications because they are designed for different applications.

If you want to update data and cause a UI update, useState is your Hook. If you need some kind of data container throughout the component’s lifecycle without causing render cycles on mutating your variable, then useRef is your solution.

The post useState vs. useRef: Similarities, differences, and use cases appeared first on LogRocket Blog.