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])
Related
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 parent component which renders everytime data in the react query changes. It is using a child component to which it passes data which should render some cards.
Inside the child component, I used the useMemo() function to make sure if it receives the same data as previously it wouldnt execute the rendering of all the cards.
This is the child component
export const VerticalCardsList = (props : VerticalCardsProps) => {
const VerticalCards = useMemo(() => {
console.log('Executing the useMemo function')
}, [props.verticalCardsData])
return VerticalCards }
The parent component calls this in a way like this
<VerticalCardsList verticalCardsData={props?.cardsResponse?.data?} />
Any suggestions with this strange situation would be greatly appreciated.
I have a parent component that listens to the callback status from a child component, and renders a progress indicator based on the status. The child component is an editor and uses memo to prevent re-rendering. This is required by the editor package. Everything works fine when the parent and child are fist mounted, but if the parent is unmounted and remounted, it doesn't render the state changes triggered by the child callback.
I've tried various things to force a re-rendering, but nothing seems to work. I added a counter to the state and a delay on updating the state. I use ref as a value since the state is being accessed in a closure.
The child component is an editor that listens to websocket information and calls handleStatus callback asynchronously. The child component is working fine after the remount and sending the updates to the handleStatus callback.
Parent Component:
const [isConnected, setIsConnected] = useState<number>(0);
const forceUpdate = useRef<number>(0);
const statusRef = useRef('connected')
const handleStatus = (status: string) => {
if ('connected' !== status) {
forceUpdate.current += 1;
setTimeout(()=>{
setIsConnected(forceUpdate.current);
}, 100)
statusRef.current = status;
}
}
<div style={{width: '100%'}} >
{statusRef.current === 'connected' ? null : <CircularProgress />}
<ChildComponent key={roomId} roomId={roomId} handleStatus={handleStatus}/>
</div>
From the debugger, I can see that handleStatus is being called and setIsConnected is being called with a new value. But the parent does not render when the state is changed.
Again, the parent only stops listening to the state updates after it was remounted. On the first mount, the parent renders on every state update.
Any suggestions where to look would be greatly appreciated.
Edit:
I'm using the same key for the parent component, so react should remount the existing component instead of creating a new component. From what I can see, it does look like it's remounting the same component since the ref retains its value and the callback from the child component is still valid. The state value is stale, but that's expected since it's in a closure. The only thing that's not making sense is that I can't seem to trigger a rerender by updating the setState. I even tried a state function incase the setState was getting stale too, but that didn't work either.
you need to use useEffect and as dependency put statusRef variable
useEffect(() => {
}, [statusRef])
<ChildComponent key={roomId} roomId={roomId} handleStatus={handleStatus}/>
I am implementing a component in functional components where there are several other child components in it passing data to each other. I need to pass data from parent to child component and call some function there to use it.
In class componenets we use componentdidupdate but could not understand how to do in functional component.
One idea is to use useEffect hook but could not do with it.
Im going to take a stab here, because we dont have context or code to go with.
useEffect accepts a dependency array to which it will react when a value or object reference changes
const ChildComponent = (props) => {
const {
valuePassedFromParent
} = props;
const actionFunction = (value) => {
//perform some tasks with value passed from parent when it changes
}
//this is similar to componentDidUpdate, but it reacts to changes in valuePassedFromParent though props since its in the useEffect dependency array
useEffect(() => {
//do something with valuePassedFromParent
actionFunction(valuePassedFromParent);
},[valuePassedFromParent]);
return (
<div>
</div>
)
}
You cas use useEffect to reproduce the behavior of componentdidupdate like this :
const [myState,setMyState] = useState();
useEffect(() => {
...
},[myState]);
The function use effect will run every time myState will be updated, like componentdiduptate would have do.
In your case, the state is given by the parent component if I understand well, so just replace the myState in the array dependency by the state given through the prop of your child component.
I have tried this pattern.
ParentComponent
...
render(
return <ChildComponent newProps="newPropsValue />)
ChildComponent
...
ComponentWillReceiveProps{
this.setState({"propsKey": "newPropsValue"})
}
As far as I understand the initial component rendering is triggered by the props change, and as setState is asynchronous (for some reason), the rendering with the new state update is not done on the first pass.
However what I don't understand is why when it finally decides to update the state, it doesn't rerender the component. I thought state changes that are caused by setState always trigger a rerender.
So in the end I have a component that uselessly rerenders before the state is actually changed, and then does nothing when/if(?) the state is updated. I don't understand this behaviour at all.
setState will trigger componentUdpate -> componentWillUpdate -> render. props change will trigger componentWillReceiveProps before this chain. You can have a look here at this image about React lifecycle. You can see the different how React behave on props and state.
So:
However what I don't understand is why when it finally decides to update the state, it doesn't re-render the component.
Updating state by setState will trigger the render function (re-render). And props also trigger render as well.
Following your code:
componentWillReceiveProps:
this.props.newProps="newPropsValue"
this.state.propsKey="newPropsValue"
render: as above, nothing change.
If any event of childComponent setting propsKey by setState (onClick, onChange ...). Assuming setState({propsKey: "anotherValue"}). Then render will be triggered again with this.state.propsKey="anotherValue and this.props.newProps="newPropsValue"
Now let's update your childComponent's props within parentComponent, assuming newProps="latestPropsValue":
Before componentWillReceiveProps:
this.props.newProps="latestPropsValue"
this.state.propsKey="anotherValue"
After componentWillReceiveProps:
this.props.newProps="latestPropsValue"
this.state.propsKey="latestPropsValue"
How do I force a child component to rerender when given new props values?
If your render is using state then setState inside render. And if you are using props inside render, it also being updated accordingly
I have found a nice solution using key attribute. If we changed key property of a child component or some portion of React Component, it will re-render entirely. It will use when you need to re-render some portion of React Component or re-render a child component depending on props or state. Here is a example. I will re-render the full component.
import React, { useState, useEffect } from "react";
import { PrEditInput } from "./shared";
const BucketInput = ({ bucketPrice = [], handleBucketsUpdate, mood }) => {
const data = Array.isArray(bucketPrice) ? bucketPrice : [];
const [state, setState] = useState(Date.now());
useEffect(() => {
setState(Date.now());
}, [mood, bucketPrice]);
return (
<span key={state}>
{data.map((item) => (
<PrEditInput
key={item.id}
label={item?.bucket?.name}
name={item.bucketId}
defaultValue={item.price}
onChange={handleBucketsUpdate}
mood={mood}
/>
))}
</span>
);
};
export default BucketInput;