Reputation: 111
When I use a setState hook inside a function that I use as a callback for a component, I get the Too many re-renders error from React.
Below is a useless example to demonstrate what I'm trying to do.
I have an array of Person objects, and an array of Pets. Each pet has an owner (=Person). I want to show the name of the Person and the pet he owns.
I loop over the Persons array, and for each person there should be a TestComponent. TestComponent expects the name of a Person and the name of a Pet.
The Pet name is calculated in a callback function getPetByOwnerName
which will loop over all Pets and if the given Person name matches the Pet's owner name, it will return that petname.
IF a Person has no pets, it should add an empty Pet object to the Pets list using setPets()
.
setPets()
inside the getPetByOwnerName
function is causing the Too many re-renders
error.
It might not make any sense of doing it this way in this example but it's to recreate an issue I have in a bigger project.
import React, { useState } from 'react'
import TestComponent from './TestComponent'
const TestPage = () => {
const [persons, setPersons] = useState([{name: 'Tom', age: 35}, {name: 'Fred', age: 50 }])
const [pets, setPets] = useState([{owner: 'Tom',name: 'Doggo'}])
const getPetByOwnerName = (name) => {
let petName = null
pets.map((pet, index) => {
if (pet.owner === name) {
petName = pet.name
}
return pet
})
if (!petName) {
const pet = {
owner: name,
name: ''
}
const updatedPets = pets
pets.push(pet)
setPets(updatedPets)
}
return petName
}
return (
<React.Fragment>
{
persons.map((person, index) => (
<TestComponent key={index} personName={person.name} petName={getPetByOwnerName(person.name)}></TestComponent>
))
}
</React.Fragment>
)
}
export default TestPage
TestComponent:
import React from 'react'
const TestComponent = ({ personName, petName }) => {
return (
<React.Fragment>
<p>{personName} owns pet: {petName}</p>
</React.Fragment>
)
}
export default TestComponent
Upvotes: 0
Views: 837
Reputation: 765
Its probably better to handle this sort of functionality inside a use effect. You could watch for changes in persons then update pets accordingly. Something like below.
But your actual problem is in this line if (!petName)
. When petName is an empty string it gets in here and sets the state forever. You need a check like if(!petName && petName !== '')
useEffect(() => {
persons.map(person => {
const petIndex = pets.findIndex(p => p.owner === person.name);
if (petIndex === -1) {
const pet = {
owner: person.name,
name: ''
}
setPets(prevPets => ([...prevPets, pet]))
}
})
}, [persons]);
Also as a side note when using the previous state to update the state its better to use the callback version (see the updated set pets in the example above)
Since this is just an example of your actual issue its probably best to put a debugger;
just before you set your state and see why you are setting it so often.
Upvotes: 1