Hi Team,
I have started using the hooks. And come across a scenario where i have to change the state of the parent component from the child component . But I am maintaining the parent component state using useState hook . So i am not getting access to the function to change this state from the child component . Can you provide information on how can I achieve this.
Thanks
Girish hT S
Hi!
There's no difference in this scenario between function and class components. In both cases, you want to pass a callback function down to the parent. Let's begin with a class example:
class Parent extends React.Component {
state = { value: "" }
handleChange = (newValue) => {
this.props.setState({ value: newValue });
}
render() {
// We pass a callback to MyInput
return <MyInput value={this.state.value} onChange={this.handleChange} />
}
}
class MyInput extends React.Component {
handleChange = (event) => {
// Here, we invoke the callback with the new value
this.props.onChange(event.target.value);
}
render() {
return <input value={this.props.value} onChange={this.handleChange} />
}
}
With hooks, we can follow the same pattern and pass a callback function down to MyInput:
function Parent() {
const [value, setValue] = React.useState("");
function handleChange(newValue) {
setValue(newValue);
}
// We pass a callback to MyInput
return <MyInput value={value} onChange={handleChange} />;
}
function MyInput(props) {
function handleChange(event) {
// Here, we invoke the callback with the new value
props.onChange(event.target.value);
}
return <input value={props.value} onChange={handleChange} />
}
Thanks Philip . I had followed the same approach before posting this question . But i had made small typo while using the use state ( used { } instead of [ ]). Because of that it was not working.
Type Error one :
const {value, setValue} = React.useState("");
Correct One:
const [value, setValue] = React.useState("");
After making the change it started working
Thanks
Girish T S
@philipp-spiess what if in your scenario the developer wanted to avoid unnecessarily re-rendering the child component (because handleChange is created on each render and is a prop of the child)?
The only ways to generally solve this problem seems to be by using useReducer, which allows the event argument (payload in the action), since there's no way to "Lift up state" because the event can't be controlled by the parent: https://reactjs.org/docs/lifting-state-up.html
Or am I missing something?
@evbo Depends on what the function is doing! Check out useCallback to create a memoized function which won't change unless the dependencies change.
If there are lots of dependencies than you are right, a useReducer and passing the stable dispatch function down might be a better option. The state can live in the parent and you call dispatcher with the event (or a subset of the event data necessary to calculate the next state).
Thanks helping to wrap my head around react!
I suppose there are a couple other tricks that may be useful in special scenarios where the child must modify state based on some event or data not owned by the parent:
These cases are outlined nicely here: https://www.codebeast.dev/usestate-vs-useref-re-render-or-not/#what-causes-re-rendering
Also, side note: I don't always like the coding convention with useReducer. I think it's a matter of taste having all the state change "business logic" colocated. Did anyone ever consider allowing useCallback to permit sending an argument? e.g:
const memoizedCallback = useCallback(
(event) => {
doSomething(a, b, event);
},
[a, b],
);
I'm pretty certain that this is a valid pattern! Especially since it is equivalent to this useMemo version:
const memoizedCallback = React.useMemo(() => {
return event => {
doSomething(a, b, event);
};
}, [a, b]);
Thank you, all of the docs had me confused no arguments should be provided.
I guess the author was trying to keep it tl;dr friendly, so this is good to know!
I tried this solution, but it only works on the first invocation in my child component.
const Parent = () => {
const [carouselIndex, setCarouselIndex] = useState(0);
const [translateValue, setTranslateValue] = useState(0);
const handleChangeHero = () => {
console.log("I'm in the parent component"); // This line gets called successfully every time it's invoked from the child, so the child and parent seem to be communicating.
setCarouselIndex(carouselIndex + 1); // carouselIndex gets updated correctly on the first call (going from 0 to 1), but on every subsequent call from the child, this line gets ignored.
setTranslateValue(translateValue + -(slideWidth())); // Same problem with this line: gets called on the first invocation only, then gets ignored on subsequent calls.
}
return (
<Child changeHero={handleChangeHero} />
)
}
const Child = props => {
const handleChangeHero = () => {
console.log("I'm in the child component"); // This line is called successfully every time this handler is invoked in the child. It's being used with "react-with-gesture".
props.changeHero();
};
return (<div>I'm a child</div>);
}
How do I get setCarouselIndex to get called every time in the parent, and have that state value updated?
Philip, this is great, but I tried something similar with multiple inputs and it doesn't work. If you could provide an example with multiple inputs that use one handleChange function based on their name, that would be super helpful! Thanks!
Hi, I have a problem when trying to update the parent state via child component in a functional component.
`const departChanged = (e) => {
const selectedPoint = e.suggestion.latlng
const newMarkers = {...markers}
newMarkers[0] = new L.marker(selectedPoint, {icon: blueIcon})
setMapCenter([selectedPoint.lat, selectedPoint.lng])
setMarkers(newMarkers)
}`
I have this method in the parent component called from the child component but all the state values I access are the BASE values of each state variables, even if they are correctly changed beforehand.
Any ideas ?
How could i test a similar scenario? I have already written functionality to pass useState setter method and value as props to child component. These values are set in the child component. How can i replicate this in a unit test with jest? So far i have
it('button becomes enabled when there are selected sections', () => {
const initState = [];
const setState = jest.fn((section) => [...initState, section]);
const useStateMock = (initState) => [initState, setState];
jest.spyOn(React, 'useState').mockImplementation(useStateMock);
const wrapper = getCourseListViewWrapper();
const courseList = wrapper.find(CourseList);
courseList.props().updateSelectedSections(sections[0]);
expect(wrapper.find('[id$="_FooterRegisterButton"]').props().disabled).toBe(false);
});
The last line fails, the mock useState func fires yet the state in parent doesnt seem to reflect the change, disabled is still true but should now be false. The getCourseListViewWrapper(); is return a shallow render via enzyme.
Most helpful comment
Hi!
There's no difference in this scenario between function and class components. In both cases, you want to pass a callback function down to the parent. Let's begin with a class example:
With hooks, we can follow the same pattern and pass a callback function down to MyInput: