0

I've tried different solutions to similar problems provided here and none of them worked, whenever i try to add to an array or set a state to a new array using hooks, i always get an error caused by too many re renders. no matter if i wrap my logic in arrow functions, or try adding to prevState, no matter if i use concat, push, map, filter always the same result. So i thought to create my array first, before even declaring the state variable, like that:

const newCompletedItems = data.map(obj => obj.completed ? obj.id : null).filter(item => item);
const [completedItems, setCompletedItems] = useState(newCompletedItems);

No error messages there, but i get an array with 0 items, while newCompletedItems is an array with multiple items (as expected).

Basically i need to set the state to an array that is an outcome of a function and then in future i'll need to setCompletedItems again, by adding to/ substracting from completedItems array.

2
  • 2
    Pretty sure this isn’t the code that’s causing a rendering recursion. Are you any point updating completedItems with a useEffect that has itself as a dependency, or any logic of a similar nature that will lead to recursion? Commented Oct 2, 2022 at 13:35
  • no , this code is not causing recursion, there were 5 -10 different attempts that did, so i changed my approach, I created the array and then tried to set the state to that array, didn't get any errors this time, but instead got an array with 0 items, while the array that i'm passing to the state has multiple itmes Commented Oct 2, 2022 at 14:00

2 Answers 2

1

The lack of code examples makes it quite difficult to spot the problem (because we don't know what else is going on in your component)

But here is an educated guess to what was the cause of some of your problems (especially the rerender ones):

In Javascript arrays are passed by reference not value.

so let's say you have a component as follows:

function Component (data) {
  const [array, setArray] = useState()
  setArray(data.map(x => x))

  return (<div></div>)
}

let's see what happens on the inital-render of this Component.

array and setArray get initialized by the useState hook. Then we use setArray() to change our state to data.map(x => x) - let's call this array DataArray1. State change causes a rerender, so let's do a rerender.

1st rerender of the Component:

the state variable array will be set to what it latest value was - so in our case since we just set array to dataArray1, our state is now = DataArray1. Then we use setArray() to change our state. React will compare the current state to the new state and only rerender if they are not the same. But since arrays are passed by reference the comparison dataArray1 === data.map(x => x) returns false. So React is like: "ok those values are different so let's change the state to this new (completely different) value. And since our state changed let's do a rerender.

so here we go again; 2nd rerender:

and since I am not really in the mood to type out an infinite recursive function we will stop here.

You can solve that problem by wrapping it all in an useEffect() hook with empty dependency array - so that it will only get called on initial render. Or just pass the array to the useState hook as initial value like so: useState(data.map(x => x))

The important thing to remember is: ARRAYS ARE PASSED BY REFFERENCE!!! (all objects are for that matter)

A basic tip: In components everything should be in hooks (most of the time)

Sign up to request clarification or add additional context in comments.

2 Comments

thanks for the explanation of why do i get re render, it's very useful, but if i wrap the code inside useEffect i still get an empty array, just like when i set the state directly with setCompleted (despite the fact that the array i'm setting the state to is not empty. Its interesting though, because if i change the code slightly, for example from map to filter or vice versa, while hosting the project in local server, then the correct array is being logged to the console.
and if i use const arr = data.map(obj => obj.completed ? obj.id : null); useEffect(() => { setCompletedItems(completedItems => [...completedItems, arr]); }, []); then i get an array of 2 elements, each element being empty array.
0

what finally worked for me, was to use useEffect as advised, but instead of passing empty dependency array as a second argument, i passed in the actual array that i'm trying to set the state to.

Here's the code with two of these useEffects (variable names changed since original post):

const [toDosData, setToDosData] = useState([]);
const [completedItems, setCompletedItems] = useState([]);     

useEffect(() => {
    data && setToDosData(data);
}, [data]);

useEffect(() => {
    toDosData.length && setCompletedItems(toDosData.filter(data => data.completed))
}, [toDosData])

So basically when i was passing empty dependency array i was getting results after first render, for some reason the expected result comes only after 2nd render.

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.