Why this setState caused infinite loop?

Viewed 243

I created a react component with rendering call details, in component I use useEffect to set the callInfo state, then it caused infinite loop, even I use [] as second parameter, can anyone help me fix this, thanks!

import { useLocation } from "react-router-dom";
import { useState, useEffect } from "react";

const ActivityDetail = ({ onToggleArchived }) => {
  const { call } = useLocation().state;
  const [callInfo, setCallInfo] = useState(null);
  console.log({...call});

  useEffect(() => {
    setCallInfo({ ...call });
  }, [])

  return (
    <div>
      <h3 className="title">Call Details</h3>
      <hr />
      {
        callInfo && <div>
          <p>From: {callInfo.from}</p>
          <p>To: {callInfo.to}</p>
          <p>Time: {callInfo.created_at}</p>
          <button onClick={onToggleArchived(callInfo.id)}>
            {callInfo.is_archived ? "Unarchive" : "Archive"}
          </button>
        </div>
      }
    </div>
  )
}

export default ActivityDetail

This is error information: Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

2 Answers

The problem lies within your return:

<button onClick={onToggleArchived(callInfo.id)}>
   {callInfo.is_archived ? "Unarchive" : "Archive"}
</button>

Here, you are calling the function onToggleArchived which presumably (it's not in the code you posted) does state updates.

how to fix it: wrap it in an arrow function

<button onClick={()=>onToggleArchived(callInfo.id)}>
   {callInfo.is_archived ? "Unarchive" : "Archive"}
</button>

EDIT: In addition to the original answer about misusing state (which you need to correct), I missed the point that you were calling the function instead of wrapping it:

 <button onClick={() => onToggleArchived(callInfo.id)}>
 // instead of 
 <button onClick={onToggleArchived(callInfo.id)}>

ORIGINAL ANSWER

in component I use useEffect to set the callInfo state

But this is a problem because call is not component state - it's coming from useLocation(). Just let it come from there and remove the component state stuff altogether.

i.e. treat it as if it were a prop.

import { useLocation } from "react-router-dom";
import { useState, useEffect } from "react";

const ActivityDetail = ({ onToggleArchived }) => {
  const { call: callInfo } = useLocation().state;

  return (
    <div>
      <h3 className="title">Call Details</h3>
      <hr />
      {
        callInfo && <div>
          <p>From: {callInfo.from}</p>
          <p>To: {callInfo.to}</p>
          <p>Time: {callInfo.created_at}</p>
          <button onClick={() => onToggleArchived(callInfo.id)}>
            {callInfo.is_archived ? "Unarchive" : "Archive"}
          </button>
        </div>
      }
    </div>
  )
}

export default ActivityDetail

Related