1

I’ve got a useCallback that updates state, but because it requires that state as a dependency, it creates an infinite loop when it updates the state. I’m using useImmer here, but it happens when using plain useState, too.

const [panels, updatePanels] = useImmer({
    activePanel: 0,
    validPanels: [],
});
const onValidatePanel = useCallback(isValid => {
    const panelIndex = panels.validPanels.indexOf(panels.activePanel);

    // Add
    if (panelIndex === -1 && isValid) {
        updatePanels(draft => {
            draft.validPanels.push(draft.activePanel);
        });
    // Remove
    } else if (panelIndex > -1 && !isValid) {
        updatePanels(draft => {
            draft.validPanels.splice(panelIndex, 1);
        });
    }
}, [panels]);

Basically when an index is added or removed, panels changes, triggers onValidatePanel again, and re-adds the index, and on and on…

How might I work around this?

5
  • Either separate the panels state into separate activePanel and validPanels state or don't tag panels as a dependency of your callback. I would prefer the former. Commented Jul 13, 2021 at 22:09
  • Can you show how the onValidatePanel callback is hooked up? Because changing panels will just rebuild the callback but it won't actually trigger it... Commented Jul 13, 2021 at 22:15
  • I'm not super familiar with immer (or the useImmer hook), but my hunch is that if you could access the panels state from draft in the functional update you could completely eliminate the need for panels as a dependency and eliminate the dependency cycle. Commented Jul 13, 2021 at 22:20
  • 1
    If you want to compute the value of state using previous state (e.g. panels.validPanels), I'd look at using the functional form of setState. You can receive previous state as an arg rather than specify it as a dependency. (reactjs.org/docs/hooks-reference.html#functional-updates) Commented Jul 13, 2021 at 22:20
  • useCallback should not by itself trigger an infinite loop. That's only going to happen if you have it in the dependency array of a useEffect, and call it unguarded from within that effect. I presume you have one but have not included it? The functional state update is probably the right way to go anyway, but something else to consider. Commented Jul 13, 2021 at 22:46

1 Answer 1

1

I don't think you need the dependency array populated at all, you can access the panels state from the draft copy in the functional state updater function.

const onValidatePanel = useCallback(isValid => {
  updatePanels(draft => {
    const panelIndex = draft.validPanels.indexOf(draft.activePanel);

    if (panelIndex === -1 && isValid) {
      // Add
      draft.validPanels.push(draft.activePanel);
    } else if (panelIndex > -1 && !isValid) {
      // Remove
      draft.validPanels.splice(panelIndex, 1);
    }
  });
}, []);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.