Reputation: 330
I have a parent component that passes a callback function to child component so that whenever the state of an array changes in child component, the callback will be called. For some reason, whenever the first time I change the state in child component, it leads to an infinite loop where the callback keeps getting called forever.
Here is the real code below.
I have defined a mutation to set the list of similarBooks when user adds a new book in the list. Child component keeps track of the list. Initial value of similarBooks is provided by parent component. Child component has a form to add books into the similarBooks list. Whenever the state of that list changes, useEffect in child component gets called which in turn calls the callback function provided by the parent function. The callback function executes the setSimilarBooks mutation.
One interesting thing is that if I just move onChangeSimilarBooks callback into the Child component, then the issue doesn't exist anymore. Or if I keep the callback in parent, but don't call the setSimilarBooks mutation, then also issue disappears.
Parent component:
const [setSimilarBooks, { data: mutationSimilarBooksData,
loading: mutationSimilarBooksLoading, error: mutationSimilarBooksError }]
= useMutation(setSimilarBooksMutation);
const onChangeSimilarBooks = (values) => {
console.log(values);
if (!mutationSimilarBooksLoading) {
setSimilarBooks({
variables: {
bookId: bookId,
similarBooksIds: values.map(value => value.id),
},
});
}
}
Parent has a child component:
<ChipsComponent
key="{bookDetails.book.name}_similarbooks"
initialValues={bookDetails.book.similarBooks}
suggestions={booksList.books}
allowNew={false}
onChangeCallback={onChangeSimilarBooks}
userFriendlyValue={userFriendlyValueOfBook}
/>
Child Component:
const [values, setValues] = useState(initialValues);
useEffect(() => {
onChangeCallback(values);
}, [values]);
...
...
return (
<div className="container">
{values.map((book, index) => (
<div className="books" key={index}>
{userFriendlyValue(book)}
<button onClick={() => deleteTag(index)}>x</button>
</div>
))}
<div className="newTag">
<input
value={input}
placeholder="Enter a tag"
onKeyDown={onKeyDown}
onKeyUp={onKeyUp}
onChange={onChange}
/>
{suggestionsActive && <Autocomplete />}
</div>
</div>);
I would like for the callback to get called only once every time the state really changes.
Upvotes: 2
Views: 587
Reputation: 202751
It's not overtly clear why moving the callback and useMutation
hook into the child and calling setSimilarBooks
directly, or keeping the callback in the parent and not calling setSimilarBooks
would trigger this. The only possibility I see from the code is that the onChangeSimilarBooks
callback isn't a stable reference. You could wrap it in a useCallback
hook to memoize it.
Example:
const onChangeSimilarBooks = useCallback((values) => {
console.log(values);
if (!mutationSimilarBooksLoading) {
setSimilarBooks({
variables: {
bookId,
similarBooksIds: values.map(value => value.id),
},
});
}
}, [mutationSimilarBooksLoading, setSimilarBooks]);
Upvotes: 0
Reputation: 1565
Reference to the function onChangeSimilarBook
changes every render. You have somehow make it stable by reference. You could try to use useEvent
.
Upvotes: 1