Reage renderização desnecessária do elemento

Sep 30 2020

Acabei de começar a aprender React e estava montando um pequeno aplicativo que faz chamadas para uma API de cotações. A API possui um ponto de extremidade que retorna uma citação aleatória. Quando o aplicativo é carregado inicialmente, ele faz uma chamada para a API e mostra uma cotação, e há um botão que pode ser clicado para obter uma nova cotação aleatória (nova chamada para a API).

Eu tenho um componente raiz chamado App. Este componente possui um componente QuoteWrap como filho. O componente QuoteWrap tem dois filhos: o botão que é usado para obter uma nova citação aleatória e um componente Citação que mostra o autor da citação e a própria citação. Este é o código dentro do componente QuoteWrap:

export default function QuoteWrap() {
    const { quoteData, isLoading, fireNewCall } = useQuote();

    const handleClick = () => {
        fireNewCall();
    };

    return(
        <>
            <button onClick={handleClick}>Get random quote</button>
            
            { isLoading ? 
                <h2>Loading...</h2>
            :
                <Quote author={quoteData.author} quote={quoteData.quote} />
            }
        </>
    );
}

useQuote () é um gancho personalizado que gerencia as chamadas para a API e retorna 3 valores: 1- os dados, 2- se uma chamada estiver em andamento e 3- uma função para fazer uma chamada para a API.

Obviamente, toda vez que o botão é clicado, todo o componente QuoteWrap é renderizado novamente (conforme as alterações quoteData e isLoading). Mas, realmente, o botão não precisa ser renderizado novamente, pois nunca muda.

Então pensei: ok, posso mover o botão até o componente App. Mas eu não tenho acesso à função fireNewCall no gancho useQuote.

Como posso evitar que o botão seja renderizado novamente? É mesmo importante neste caso ou estou ficando obcecado demais com os re-renderizadores do React?

Obrigado!

Respostas

Benjamin Sep 30 2020 at 01:18

Seu componente será renderizado novamente toda vez que a função handleClick mudar, ou seja, toda vez que QuoteWrap for renderizado.

A solução é o gancho useCallback. useCallback retornará a mesma função para handleClick, toda vez que QuoteWrap for renderizado, desde que as dependências não tenham mudado.

https://reactjs.org/docs/hooks-reference.html#usecallback

Você o usaria assim:

  const handleClick = useCallback(() => {
    fireNewCall();
  },[fireNewCall]);

fireNewCall é a dependência, portanto, desde que useQuote retorne uma função fireNewCall estável, seu botão não será renderizado novamente, já que a propriedade handleClick não mudou.

VietDinh Sep 30 2020 at 01:15

Eu acho que você pode ficar muito obcecado com as re-renderizações do React. O botão deve ser renderizado novamente porque, handleClickdeve ser alterado quando fireNewCallalterado para algum caso. Mesmo que, handleClicknunca seja alterado. Não é necessário pensar em uma re-renderização de elemento.

BernardLeech Sep 30 2020 at 01:24

Praticamente o que Benjamin e Viet disseram - em seu código original, uma nova função é atribuída para handleClick em cada renderização. Você pode usar React.useCallbackpara manter a referência da função original e apenas atualizá-la quando algo na matriz de dependência muda - neste caso, apenas fireNewCallprecisa ir para a matriz de dependência.

Mas, como diz o Viet, não fique muito obcecado com isso. O uso React.useCallbackpode até tornar seu código mais lento. Confira Kent C. Dodds When to useMemo e useCallback post para mais informações.