Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Read prev props: Too many renders

I want someone to confirm my intuition on below problem.

My goal was to keep track (similar to here) of props.personIdentId and when it changed, to call setSuggestions([]).

Here is code:

function Identification(props) {
    let [suggestions, setSuggestions] = useState([]);

    let prevPersonIdentId = useRef(null)
    let counter = useRef(0) // for logs

    // for accessing prev props
    useEffect(() => {
        prevPersonIdentId.current = props.personIdentId;
        console.log("Use effect", props.personIdentId, prevPersonIdentId.current, counter.current++)
    });


    // This props value has changed, act on it.
    if (props.personIdentId != prevPersonIdentId.current) {
        console.log("Props changed", props.personIdentId, prevPersonIdentId.current, counter.current++)

        setSuggestions([])
    }

But it was getting in an infinite loop as shown here:

enter image description here

My explanation why this happens is that: initially when it detects the prop has changed from null to 3, it calls setSuggestions which schedules a re-render, then next re-render comes, but previous scheduled useEffect didn't have time to run, hence the prevPersonIdentId.current didn't get updated, and it again enters and passes the if condition which checks if prop changed and so on. Hence infinite loop.

What confirms this intuition is that using this condition instead of old one, fixes the code:

 if (props.personIdentId != prevPersonIdentId.current) {
        prevPersonIdentId.current = props.personIdentId;
        setSuggestions([])
 }

Can someone confirm this or add more elaboration?

like image 277
Giorgi Moniava Avatar asked Nov 25 '25 21:11

Giorgi Moniava


2 Answers

useEffect - is asynchronous function! And you put yours condition in synchronous part of component. Off course synchronous part runs before asynchronous.

Move condition to useEffect

useEffect(() => {
    if (personIdentId !== prevPersonIdentId.current) {
      prevPersonIdentId.current = personIdentId;
      console.log(
        "Use effect",
        personIdentId,
        prevPersonIdentId.current,
        counter.current++
      );
      setSuggestions([]);
    }
  });

It could be read as: When mine component updates we check property personIdentId for changes and if yes we update ref to it's value and run some functions

like image 59
Travnikov.dev Avatar answered Nov 28 '25 10:11

Travnikov.dev


It looks like this is what's happening:

  1. On the first pass, props.personId is undefined and prevPersonIdentId.current is null. Note that if (props.personIdentId != prevPersonIdentId.current) uses != so undefined is coerced to null and you don't enter the if.
  2. Another render occurs with the same conditions.
  3. props.personId now changes, so you enter the if.
  4. setSuggestions([]) is called, triggering a re-render.
  5. loop forever

Your useEffect is never invoked, because you keep updating your state and triggering re-renders before it has a chance to run.

If you want to respond to changes in the prop, rather than attempting to roll your own change-checking, you should just use useEffect with the value you want to respond to in a dependency array:

useEffect(() => {
       setSuggestions([])
 }, [props.personId] );
like image 21
Will Jenkins Avatar answered Nov 28 '25 10:11

Will Jenkins



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!