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:
<App />
component createshandleChange
method and passes it to the<Comp />
- Useffect in the
<Comp />
will be called after initial rendering and from there<App />
component's handleChange method will be called. - 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. - 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:
Hold the error state in
Child
, notParent
, and don't recomputeerror
ifdata
is unchanged.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
For Loop With Two Array in JavaScript
Pass Node Js Variable to Ejs Template
Size of Json Object (In Kbs/Mbs)
Chrome Blank Pdf Print Preview
Google Charts: Move Legend Position
React Enable Button After All Form Fields Are Not Empty
How to Display an Image in Two Pages in Pdf Using Jspdf
Firebase.Firestore() Is Not a Function When Trying to Initialize Cloud Firestore
Disable Pdf Download and Save Option
How to Iterate Through Json Nested Objects With Ngfor
How to Toggle (Hide/Show) Sidebar Div Using Jquery
How to Combine First Name and Last Name in JavaScript
Whenever I Click Outside the Element It Should Close
Charts.Js Graph Not Scaling to Canvas Size
Make Angular.Foreach Wait for Promise After Going to Next Object