Home > Net >  Getting a "Too many re-renders" error with React Hooks
Getting a "Too many re-renders" error with React Hooks

Time:09-30

I'm struggling with React Hooks here. I looked online, but couldn't figure out how to adapt the examples to my code. I have the following component which triggers a "Too many re-renders" error:

const EmailVerification = () => {
  const [showMessage, setShowMessage] = useState(true);
  const [text, setText] = useState("...Loading. Do not close.");

  const { data, error } = useQuery(VERIFY_EMAIL);
  if (error) {setText(genericErrorMessage);}
  if (data) {setText(emailVerificationMessage);}

  return (
    <Wrapper>
      <Message setShowMessage={setShowMessage} text={text} />
    </Wrapper>
  )
}

How can I reorganize my code to avoid this error? I know that the useEffect hook should be used to perform side effects, although I wouldn't know how to use it in this case (supposing it is necessary).

CodePudding user response:

The error is triggered because you are using setText directly in the render function. This function renders the component after calling it. Because in the next render, data and error are still set, it calls setText again.

You are right about useEffect. With useEffect you can make sure that the setText function is only being called when a change occurs in the data. In your case, that is for the data and/or error variables.

import { useEffect } from 'react';

const EmailVerification = () => {
  const [showMessage, setShowMessage] = useState(true);
  const [text, setText] = useState("...Loading. Do not close.");

  const { data, error } = useQuery(VERIFY_EMAIL);
  
  useEffect(() => {
    if (error) setText('message');
    if (data) setText('emailVerificationMessage');
  }, [error, data]);
  
  return (
    <Wrapper>
      <Message setShowMessage={setShowMessage} text={text} />
    </Wrapper>
  )
}

However, since you are only changing the text variable using already existing props, you can also do this in JS(X) only:


const EmailVerification = () => {
  const [showMessage, setShowMessage] = useState(true);
  const { isLoading, data, error } = useQuery(VERIFY_EMAIL);
  
  const text = isLoading ? 'Loading... Do not close' : error || !data ? 'Error message' : 'emailVerificationMessage';

  return (
    <Wrapper>
      <Message setShowMessage={setShowMessage} text={text} />
    </Wrapper>
  )
}

This uses a nested ternary operator (not a fan) which can be replaced with any other method.

CodePudding user response:

setText will cause a rerender and will be called again on the next render. As I understand, you want to set the text once the query returns either an error or the data.

To avoid this, either use one rror and onCompleted that you can pass to useQuery like so :

const { data, error } = useQuery(VERIFY_EMAIL, {
  onCompleted: () => setText(emailVerificationMessage),
  onError: () => setText(genericErrorMessage)
});

and remove these two lines:

if (error) {setText(genericErrorMessage);}
if (data) {setText(emailVerificationMessage);}

or call setText in a useEffect:

useEffect(() => {
  if (error) {
    setText(genericErrorMessage)
  }
}, [error])
  • Related