Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how to change the state of parent component from child Component if parent state is maintained using useStateHook #1689

Closed
girishts opened this issue Feb 15, 2019 · 18 comments

Comments

@girishts
Copy link

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

@philipp-spiess
Copy link
Contributor

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} />
}

@girishts
Copy link
Author

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

@evbo
Copy link

evbo commented Jul 24, 2019

@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?

@philipp-spiess
Copy link
Contributor

@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).

@evbo
Copy link

evbo commented Jul 24, 2019

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:

  • have the child own the state - so that only it updates (not the whole parent)
  • have the child only modify a ref variable of the parent (ref changed by child won't cause re-render, but something else still needs to trigger state change eventually)

These cases are outlined nicely here: https://www.codebeast.dev/usestate-vs-useref-re-render-or-not/#what-causes-re-rendering

@evbo
Copy link

evbo commented Jul 24, 2019

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],
);

@philipp-spiess
Copy link
Contributor

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]);

@evbo
Copy link

evbo commented Jul 24, 2019

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!

@fsultani-atm
Copy link

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?

@estrizhiver
Copy link

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!

@baldash
Copy link

baldash commented Jun 11, 2020

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 ?

@whytekieran
Copy link

whytekieran commented Sep 24, 2020

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.

@BraisC
Copy link

BraisC commented Jan 13, 2021

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} />
}

Hi, is there any real difference between doing that and passing down directly the setter as a prop?:

return <MyInput value={value} onChange={setValue} />;

Thank you!

@vintuvishAl
Copy link

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} />
}

How to define types without using any?

@shunjizhan
Copy link

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} />
}

Hi, is there any real difference between doing that and passing down directly the setter as a prop?:

return <MyInput value={value} onChange={setValue} />;

Thank you!

also curious about this, any ideas?

@BraisC
Copy link

BraisC commented Jun 2, 2021

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} />
}

Hi, is there any real difference between doing that and passing down directly the setter as a prop?:

return <MyInput value={value} onChange={setValue} />;

Thank you!

also curious about this, any ideas?

After thinking about it, I found that the custom handler method can be even worse in certain situations, for example if you have memoized the child component, as in every rerender the handler would be different but the setter will not. This is an advantage of passing the setter instead of a handler function.

The advantage of using a handler is maybe legibility? I dont know, if the handler is only going to do a setState I would not create it.

@cjhaddad
Copy link

I'm running into an issue where when the callback is called a second time the state is stale and not updated. Any ideas on how to remedy this?

@manavm1990
Copy link

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} />
}

Hi, is there any real difference between doing that and passing down directly the setter as a prop?:

return <MyInput value={value} onChange={setValue} />;

Thank you!

I agree with the approach of passing the dispatchAction from useState directly to the child. I have seen this approach used by others, but I don't recall where.

However, this pattern is similar to directly passing a useReducer dispatcher when used in conjunction with useContext, so I don't see 👀 why this would be bad.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests