How to Avoid the Setstate()/Render() Endless Loop When Passing Data from Child to Parent

Passing data from child to parent , but unable to setState

When the parent changes state, it renders the child. When the child renders, it updates the parent state. There would be a few clever ways to fix this, but your ultimate problem is that copying data into both a parent and child's state isn't very idiomatic.

Generally, you should "hoist" any state used by both parent and child to only exist in the parent, and this "hoisting" should happen right off the API call.

In ApiCall, you could switch to

  getTagApi(){
this.callApi()
.then(res => this.sendTags(res.express))
.catch(err => console.log(err));
}

In your parent Tags component, you would need to pass the parents tag state back down.

<ApiCall getTags={this.getTags} tags={this.state.tags} />

Your ApiTags would then just use the parent's tags.

    let tagArray = this.props.tags;

You also would not want to copy this.props.tags into this.state during construction.

React expects that there be one source-of-truth for all data. If a higher-level component needs the data as well as a lower-level component, you should always hoist the data, as in, pass it up whenever it changes, and pass it back down as a prop on every render.

React Hooks - Update parent component without triggering infinite loop

You should wrap the handleChange method with useCallback hooks, so that it will be created once.

const handleChange = useCallback(() => setState(state + 1),[]);

The infinite loop happens because you have added onChange method as dependency for useEffect in the <Comp /> component.

useEffect takes array of dependencies and runs the effect if one of dependencies change.

Since you have added onchange handler as dependency, each time parent component re-renders, a new instance of handleChange method is created which is not equal to the previous handlechange method.

The component rendering flow will be like this:

  1. <App /> component creates handleChange method and passes it to the <Comp />
  2. Useffect in the <Comp /> will be called after initial rendering and from there <App /> component's handleChange method will be called.
  3. state changes in <App /> component and re-renders it. While re-rendering new instance of handleChange is created and passed on onChange prop to <Comp /> component.
  4. Since the value at previous and new onChange prop is different, useEffect will fired which again will update parent component's state and this loop continues.

To prevent this, handleChange method should be wrapped with useCallback. The callback function passed to useCallback hook will be memoized so when the child component compares the old and new prop they remain equal.

{} === {}  // false
[] === [] // false
(() => {}) == (() => {}) //false

Calling setState() with child functional component causes infinite loop

Need to bind it on constructor, when ever function binding is passed as props a new function will be created by which the props will signify a change triggering the render again

import TimeGrid from './TimeGrid';

class ScheduleView extends Component {
constructor(props) {
super(props);
this.state = {
schedule: [], // initially empty
};
this.handleGridChange = this.handleGridChange.bind(this);
)

handleGridChange(newSchedule) {
this.setState({
schedule: newSchedule,
});
}

render() {
return (
<TimeGrid
schedule={this.state.schedule}
handleGridChange={this.handleGridChange}
/>
);
}
}

How to Avoid Infinite Re-Rendering Loop When You Have to Set State in Render()?

To summarize a few comments I've seen, you should remove this.datesGetChanged() from your render function. If the parent's state must be updated when the child's state changes, then update the parent's state when the child's state changes. In your code, that would be the onChange functions on your date pickers.

render is something React does on its own schedule, and calling a state changing function inside it is a recipe for the infinite loops you describe.

To quote the React docs:

The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it’s invoked, and it does not directly interact with the browser.

render docs

Why doesn't my parent-child component relationship result in a rendering loop?

As Gabriele Petrioli points out, provided nothing else changes, you won't be in an endless cycle because a state update that changes to exactly the same value doesn't cause a re-render. But by default you'd still get the child doing the work twice (once to find the error, then again when the parent re-renders to show the error and the child gets called again).

There are at least a couple of ways to avoid that duplication:

  1. Hold the error state in Child, not Parent, and don't recompute error if data is unchanged.

  2. Memo-ize the Child so the function doesn't get called again when its props haven't changed.

Here's a version of #2 that relies on array identity (that is, it won't run Child again if the same array is provided to it):

const Child = React.memo(({data, onError}) => {
if (!someCondition(data)) {
onError("There is some error")
return null; // Or whatever
}
return (
<Chart data={data}/>
);
});

A more defensive version could check to see whether the new array is equivalent to the previous one, even if they aren't the same array:

const childPropsAreEqual = (prevProps, currProps) => {
// Where `deepEquals` is a function that does a deep equivalence
// check on the array
return deepEquals(prevProps.data, currProps.data);
};
const Child = React.memo(({data, onError}) => {
if (!someCondition(data)) {
onError("There is some error")
return null; // Or whatever
}
return (
<Chart data={data}/>
);
}, childPropsAreEqual); // <== Note providing an "are equal" function

React.js | Infinite render if I pass setState as a callback function, even after destructuring props

The reason why there is infinite loop is because you have callback as dependency in useEffect. And what you are passing to the component is you pass a new callback function on each new render, hence it always enters useEffect because of that. Since you are using classes consider passing instance method as callback instead of arrow function as here.


Also I think you are overusing refs. You could also achieve what you are doing by just storing say the id of clicked button, and then dynamically styling all buttons, e.g. inside map if id of current button is same as the one stored, use #10CB81 bg color in style object, otherwise different one.

Also there are better ways to check which btn was clicked, see here.



Related Topics



Leave a reply



Submit