I have a little problem with my code. When I render my component I've got this error on console console
Maximumupdate depth exceeded. This can happen when a component repeatedly
calls setState inside componentWillUpdate or componentDidUpdate. React
limits the number of nested updates to prevent infinite loops.
Below is part of my sample code:
MainComponent:
const saveData = (data) => {
setDataArray(prevState => [...prevState, data]);
}
return (
<Fragment>
{dataArray.map((element, index) => {
return (<MyComponent data={element} index={index} save={saveData}/>)
})}
</Fragment>
)
dataArray is an array of objects
MyComponent:
const MyComponent = (props) => {
const [data, setData] = useState(props.data);
useEffect(() => {
props.save(data);
}, [data]);
}
Is the problem in updating the state up to main component? Or I can't render my child component using map()?
Notice what happens when a child component updates its data:
It calls parent.saveData
This triggers a state change in the parent because the data array has changed
Because you didn't pass a key prop to children, React has no choice but to re-render all children with the updated data
Each child notices that its data prop has changed. This triggers the useEffect, so it also calls parent.saveData
The process repeats while the original render is still taking replace, etc, etc
Solution
Pass a key prop to each child. Do not use the index as key, instead use something that reflects the data being passed to the components
Related
Simplifying my app's components, I have a NoteList component with a particular child called FileTree. There are various states on NoteList. What I'm trying to do is to stop FileTree from re-rendering any time a state that only impacts NoteList changes. I'm getting tripped up on some functions that FileTree uses.
NoteList looks roughly like this:
const [folderName, setFolderName] = React.useState("")
...
const handleCreateFolder = React.useCallback(async (e) => {
e.preventDefault();
...
await axios.post(...call to create a folder...)
}, [])
const folderNameChangeHandler = React.useCallback((folN) => {
setFolderName(folN)
}, [])
return (
...
<FileTree prop1={prop1Value} prop2='prop2HardCoded' handleCreateFolder={handleCreateFolder} setFolderName={folderNameChangeHandler} />
)
Using the profiler, I still see that FileTree is re-rendering with every keystroke into a text input that uses setFolderName (or really, it should be using folderNameChangeHandler if I understand correctly) to change folderName.
What am I doing wrong with useCallback?
Using useCallback is not going to prevent re-renders when using setFolderName because setFolderName is going to cause a re-render every time it is called.
Check out React.memo to control when <FileTree /> re-renders.
You can use it to access previous and next props to determine if <FileTree /> should re-render.
function FileTree() {...}
export default React.memo(FileTree, (prevProps, nextProps) => {
return true // this will never re-render
// or do your custom logic to determine when it should re-render
})
I am doing a project where i have a toast function which implements toast there i call the function of fetching data from api and updating my state so that whenever i click the update feed button fetching data from api function called, updation of state and toast of success appears. Now the question is i have a component of categories post displays seperate category post inside of all post component which has the function to display toast, how could i pass the updated state,fetching data from api function from child component that is category post component to parent component that is all post component to implement toast for category component.
If I understand your question correctly -- at a high level, you're trying to figure out how to update a state variable of a parent component from within a child component. Easiest way would be with the useState hook, and then by passing the setState function to the child component.
const ParentComponent = () => {
const [state, setState] = useState([])
useEffect(() => {
// logic that will be executed every time the state variable changes
}, [state])
return <ChildComponent setState={setState} />
}
const ChildComponent = ({setState}) => {
const handleClick = () => {
setState((currentState) => currentState.concat(1))
}
return <Button onClick={handleClick} />
}
Edit: To answer your question from the comment -- a few things to point out here:
You can pass a value to useState which will be the starting value of the variable. In our example, it's an empty array
setState has access to the current state, so you can push a value to an array with this syntax: setState((previousState) => previousState.concat(val))
useEffect is a hook which is invoked whenever there's a change in the value of the dependency (or dependencies) passed in the second argument. So by including state in its dependency array, we can execute whatever logic we want every time the value of the state variable changes
I would also recommend looking into useMemo. It similarly allows you to have aspects of your component logic that are re-executed only when values of certain variables change. For example:
const ParentComponent = () => {
const [state, setState] = useState([])
useEffect(() => {
// logic that will be executed every time the state variable changes
}, [state])
const renderCards = useMemo(() => {
return state.map(val => <SomeOtherComponent val={val}/>)
}, [state])
return (
<div>
{renderCards}
<ChildComponent setState={setState} />
</div>
)
}
By wrapping the function inside renderCards in the useMemo hook, the evaluated result is "memoized". And so it won't be executed on every render, unless the variable in the dependency array changes.
Passing down setState to a child component in order to trigger a re-render in the parent component is straightforward when it's an immediate child. If the child component is nested deeper, or there are multiple components that need to react to a change in a variable (e.g. light/dark mode) -- that's when you want to look into a state management tool like Redux or Context.
There are two ways I can think of to achieve what you are trying to do here, i.e. get the child component's state in a parent component.
You can make use of refs. You should be familiar with React hooks to use this approach. The documentation is really good.
You can also make use of a global state using either Redux or Context.
I have a component which receives as a prop a setState hook from its parent component. When it calls that prop causes a re-render since parent updates its state.
The thing is that I have a useEffect inside the child component that needs to be run only when props change (props is the only dependency it has). But since its parent re-renders it will be executed even when it's not need and I don't know a way to prevent it. I think parent rerender send the props again to the child.
Attaching a basic reproduction of my issue
function Parent = () => {
const [test, setTest] = useState()
return (
<Child onClick={value => setTest(value)} propINeed={{foo: 'bar'}}/>
)
}
function Child = props => {
useEffect(() => {
//STUFF I NEED TO RUN ONLY WHEN PROPS DO REALLY CHANGE
}, [props])
//ONCLICK PROP IS CALLED, AND useEffect RUNS AGAIN, messing with my data
}
I know props don't change. So I'm pretty sure it's due to the parent re-render
Put only the propINeed into the dependency array, so the useEffect callback doesn't run on click:
useEffect(() => {
//STUFF I NEED TO RUN ONLY WHEN PROPS DO REALLY CHANGE
}, [props.propINeed])
I have a wrapping parent component that accepts some layout-related props, and a child component (representing a page) that needs to run a GraphQL query via Apollo, and update that parent's prop with the result (say the page title). The parent requiring props that I need to update is not a pattern I chose, but the implementation of a component I must use.
App = () {
const [layout, setLayout] = useState({});
return (
<Wrapper layout={layout: 'foo'}>
<PageComponent setLayout={setLayout} />
</Wrapper>
);
}
PageComponent = (props) => {
const { loading, error, data } = useQuery(QUERY); // Apollo's useQuery
useEffect(() => {
props.setPageParams({ title: data.lists[0].title });
}, [data]);
if (loading) return <p>Loading</p>;
if (error) return <p>Error</p>;
return (
<p>component!</p>
);
}
I've tried too many different approaches to write them all down here, but the results are one of two things: a loop in the useEffect (which I somewhat understand, I think passing data to useEffect is causing it to set the parent's props and re-render the child every time because it is an object and isn't been deeply compared), or the following:
Cannot update a component (`App`) while rendering a different component (`PageComponent`).
The latter happens while attempting a few approaches without useEffect, such as:
if(data && data.lists && awaitingPageLayout) {
props.setLayout({ title: data.lists[0].title });
awaitingPageLayout = false;
}
I have also tried to use useQuery's onCompleted, which I believe resulted in another loop.
Hoping for a push in the right direction, I'm out of ideas here. Appreciate your time!
According to that link: http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
render() may be triggered with new props. Could someone give me a code example for that? I cannot see how props change invoke rendering! Please not by changing the props via the state; then it is setState() that invokes render()...
Look at shouldComponentUpdate() - this is it's signature - it returns a boolean. Props is there so you can compare and manually say whether the component should update.
shouldComponentUpdate(nextProps, nextState)
For function components React.memo is the replacement for shouldComponentUpdate which is used with class components.
const myComponent = React.memo(props => {
...
code of React.FunctionComponent
...
},
(prevProps, nextProps) => prevProps.propA === nextProps.propA
);
React.memo gets two arguments shown above: a React.FunctionComponent which will be wrapped around by memo and an optional function that returns a boolean.
When the function returns true, the component will not be re-rendered. If the function is omitted then its default implementation in React.memo works like the implementation of shouldComponentUpdate in React.PureComponent. E.g. it does shallow comparison of props, the difference is that only props are taken into account because state doesn’t exist for functional components.
Using hooks is neater to show. The new props data passed to ComponentB causes a re-rendering of ComponentB:
import React, { useState } from 'react'
import ComponentB from '...'
const ComponentA = props => {
const [data, setData] = useState(0) // data = 0
handleChangeProp = item => setData(item) // data = 1
return(
<div>
<button onClick{() => handleChangeProp(1)}
<ComponentB props={data} />
</div>
)
}
Yes, when you do a setState(newState) or when you pass in changed props the component will re render, this is why you can't mutate. The following will not work because you set state with mutated state.
export default function Parent() {
const [c, setC] = useState({ c: 0 });
console.log('in render:', c);
return (
<div>
<button
onClick={() =>
setC(state => {
state.c++;
console.log('state is:', state);
return state;
})
}
>
+
</button>
<Child c={c.c} />
</div>
);
}
That code "won't work" because pressing + will not cause a re render, you mutated state and then set state with the same object reference so React doesn't know you changed anything.
This is how React detects changes, you may think that comparing {c:0} to {c:1} is a change but because you mutated there actually is no change:
const a = {c:1};
a.c++;//you "changed" a but a still is a
To indicate a change in React you have to create a new reference:
const a = {c:1};
const b = {...a};//b is shallow copy of a
a===b;//this is false, even though both a and b have same internal values
This means you can also have unintended renders because you create an object prop that may have the same value but still is a different reference than the last time you created it.
Note that even <Child prop={1} will cause Child to render if Child is not a pure component (see links at the end).
What you want to avoid is doing <Child prop={{c:value}} because every time you pass prop it'll force Child to render and React to do a virtual DOM compare even if value didn't change. The virtual DOM compare will probably still detect that Child virtual DOM is the same as last time and won't do an actual DOM update.
The most expensive thing you can do is <Child onEvent={()=>someAction(value)}. This is because now the virtual DOM compare will fail even if value and someAction did't change. That's because you create a new function every time.
Usually you want to memoize creating props in a container, here is an example of doing this with react-redux hooks. Here is an example with stateful components passing handlers.