I've observed an interesting nuance of useState that I'm wanting to understand more deeply. The scenario:
Parent component owns some some local state created via useState
Parent passes an event handler function as a prop to a child component. This handler sets state in the parent.
Child component also has local state created by useState
When a certain event happens in the child, both the callback prop and the local state setter are called
My observation is that if the callback is run first, the a re-render of the parent is triggered which clobbers setting the child state. If I reverse the order things "seem" to be fine.
Contrived example:
Parent Component
const Parent = () => {
const [, setParentData] = useState();
function onData(data: string) {
setParentData(data);
}
return (
<Child
onData={onData}
/>
)
};
Child Component
const Child = ({
onData
}) => {
const [childData, setChildData] = useState();
function onClick() {
const data = Date.now;
onData(data);
// this call gets clobbered if onData is called first
// but is fine if I swap them
setChildData(data);
}
return (
<div onClick={onClick}>{ childData }</div>
);
};
Anyone know if ensuring child state setters are called before the parent callback is sufficient to keep all state updates, or are there other async types of things here where I may still have a race condition of some sort?
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 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.
please help me figure out why the component works like that.
I have a functional component something like this:
function MyComponent(props) {
const { dataArr } = props;
[ownDataArr, setOwnDataArr] = useState([...dataArr]);
// doesn't change after changing in the state of the parent component
console.log(ownDataArr);
return (
// ownDataArr is used here
);
It receives dataArr from parent component via props (parent component contains this in state). And when changed in parent component after MyComponent rerenders, ownDataArr stays the same. What am I doing wrong?
P.S. The child component needs the state, since it must be able to change the received data without constantly sending it to the parent.
You can do this to update the state on props change
useEffect(() => {
setOwnDataArr(dataArr)
}, [dataArr])
This is because state initialize on the first render of component with the props and when the props change, we have to update the state using useEffect
function MyComponent(props) {
const { dataArr } = props;
[ownDataArr, setOwnDataArr] = useState([...dataArr]);
// add useEffect wich depend on dataArr changing,
// because initial state is memoized by useState
useEffect(() => setOwnDataArr(dataArr), [dataArr])
return (
// ownDataArr is used here
);
}
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 am trying to create a chart in D3 inside a React component. Whenever I change the state in my parent component, the child gets re-rendered even though it's props do not change or are affected in any way by the setState. I have even tried to use React.memo on the child component but it still gets re-rendered. How can I avoid the re-render if the props have not changed.
PSEUDOCODE
/* Parent component */
function parentComponent(props) {
const [a, setA] = useState({})
function customEventHandler() {
setA(); // This cause child to re-render
}
return (
// Some rendering based on the value of a
// Some custom event handler
<ChildComponent config={{}} />
)
}
/* Child component [D3 based component with useRef] */
function childComponent = React.memo((props) => {})
CodeBox link here https://codesandbox.io/s/patient-fast-unlzq?file=/src/App.js
ISSUE: The graphs get re-rendered every time the input text changes.