Understanding How Some React Hooks Work

React Hooks

Understanding How Some React Hooks Work

Como rodar o projeto:

Você pode clonar o projeto e rodá-lo localmente seguindo os passos abaixo

  1. git clone https://github.com/rafaballerini/ReactHooks.git para clonar o projeto
  2. yarn para instalar as dependências do projeto
  3. yarn start
  4. Acessar http://localhost:3000 no navegador

Como testar cada um dos hooks:

  1. Abra o arquivo App.js. É possível perceber que existem várias linhas comentadas, tanto na parte de importações, quando dentro da função.
  2. Descomente a importação referente ao hook que deseja testar e também a linha dentro da função (há outro comentário no final da linha indicando a qual hook ela pertence).

Explicação de cada Hook:

useState

É uma função para controle de estado:

  • recebe um parâmetro (valor inicial desse estado)
  • retorna uma lista com 2 variáveis (a primeira é o valor do estado em si e a segunda é a função que atualiza esse estado)
const [state, setState] = useState(0);

O setState será usado para atualizar os valores do estado, por exemplo:

  function increment(){
    setState(state + 1)
  }

useEffect

  • recebe dois parâmetros: uma função e uma lista de dependências. Quando algum elemento dessa lista for alterado, a função é executada automaticamente.
  • o retorno da função pode ser uma função. Se for, ela será executada quando o componente for desmontado
  useEffect(() => {
    console.log(state)
  }, [state])

Quando a lista de dependências estiver vazia, a função será executada no momento que o componente for renderizado.
É executado de forma assíncrona depois de uma renderização na tela.

useContext

É uma forma de mais de um componente ter acesso a uma funcionalidade/lógica do programa.
Pra isso é criado uma const Context usando o React.createContext.

const Context = createContext()

Criamos um componente que será o provedor dos dados que tem no nosso context.

export function PageContextProvider({children}) {
  const [state, setState] = useState(0);

  const increment = useCallback(()=>{
    setState(state + 1)
  },[state])

  const decrement = useCallback(()=>{
    setState(state - 1)
  },[state])

  const handleChange = useCallback((event)=>{
    setState(Number(event.target.value))
  },[state])

  return (
    <Context.Provider value={{
        state, 
        increment, 
        decrement, 
        handleChange}
        }>
        {children}
    </Context.Provider>
  )
}

Note que esse Context.Provider recebe a propriedade children e utiliza essa propriedade para representar todos os componentes.

<PageContextProvider>
    <PageInputContext/>
</PageContextProvider>

PageInputContext é um componente filho de PageContextProvider e para acessar as propriedades que ele envia é preciso chamar o useContext passando como parâmetro o Context, criado com o React.createContext

export default function PageInputContext(){
    const {state, increment, decrement, handleChange} = useContext(Context)
}

useReducer

No useState a lógica de atualização de dados fica dentro de onde ele foi chamado, já no UseReducer a lógica ficará em uma função, como essa reducer abaixo.

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return state + 1;
    case "decrement":
      return state - 1;
    default:
      return action.newState;
  }
}
  • recebe até 3 parâmetros: o primeiro é a função que altera o estado, o segundo é o estado inicial e o terceiro é uma função init caso seja necessário executar alguma coisa no momento que o estado é criado
  • retorna uma lista com 2 elementos: o valor do estado em si e o dispatch, que é a forma como iremos chamar a função para atualizar o estado, ela que dispara a chamada da função reducer
const [state, dispatch] = useReducer(reducer, 0);

Normalmente é melhor utilizar o useReducer quando se tem uma lógica complexa no state que envolve múltiplos subvalores ou quando o próximo state depende do anterior.
Ele também permite otimizar performance para alguns componentes, pois você pode passar um dispatch ao invés de callbacks.

<button onClick={() => dispatch({ type: "decrement" })}>

useCallback

Para cada alteração no valor do state, criamos um callback diferente: increment, decrement e handleChange

  • recebe 2 parâmetros: uma função e uma lista de dependências. Essa função é memorizada e apenas será rerenderezida quando um dos valores da lista de dependências for alterado.
const increment = useCallback(()=>{
    setState(state + 1)
},[state])

const decrement = useCallback(()=>{
    setState(state - 1)
},[state])

const handleChange = useCallback((event)=>{
    setState(Number(event.target.value))
},[state])

A função não ser recarregada toda hora facilita muito o processamento.

useMemo

Assim como o useCallback:

  • recebe 2 parâmetros: uma função e uma lista de dependências.
const memorizedValue = useMemo(() => {
    if(state > state2){
      return 'Maior'
    }else if(state < state2){
      return 'Menor'
    }else{
      return 'Igual'
    }
}, [state, state2])

O valor de retorno da função é memorizado e apenas será recarregado quando um dos valores da lista de dependências forem alterados.

useRef

  • retorna a referência de um objeto mutável e que existirá durante toda a vida do componente.
export default function InputTexto() {
    const inputRef = useRef(null);
    const focaInput = () => {
      inputRef.current.focus();
    };
    return (
      <div className="content refContent">
        <input ref={inputRef} type="text" />
        <button className="focusButton" onClick={focaInput}>Dá foco no input</button>
      </div>
    );
}

Para acessar o objeto é necessário usar a propriedade .current do useRef.

useImperativeHandle

Utilizado para passar um componente ref para um componente pai, devendo ser combinado com o fowardRef.

function InputTextoImperative(props, ref) {
    
    const inputRef = useRef(null);

    useImperativeHandle(ref, () => ({
        focus: () => {
          inputRef.current.focus();
        }, 
        console: () => {
          console.log('teste')
        }
    }));

    return (
      <div className="content">
        <input ref={inputRef} type="text" />
      </div>
    );
}

export default forwardRef(InputTextoImperative);

Você pode colocar um botão em App.js (componente pai) e ele chamar a função do componente filho.

<InputTextoImperative ref={inputRef}/> 
<button className="focusButton" onClick={focaInput}>Dá foco no input</button>

useLayoutEffect

É muito parecido com o useEffect, porém é disparado de forma síncrona após todas as mudanças no DOM e antes de aparecer qualquer coisa na tela.

useLayoutEffect(() => {
    console.log('layout');
}, [])

É preferível utilizar o useEffect para evitar bloqueio de atualizações visuais.

Quando usar?
Você vai perceber que o elemento ficará piscando/executando várias vezes quando utilizar o useEffect, nesse caso o ideal é alterar para useLayoutEffect.

useDebugValue

Utilizado em conjunto com a extensão React Dev Tools para mostrar o conteúdo de algum estado, como se fosse um console.log.

function useAnalyzeState(state) {
  useDebugValue(`Valor do state = ${state}`);
  return state;
}

Como o próprio nome diz, é utilizado para fazer debug.

GitHub

https://github.com/rafaballerini/ReactHooks