hello_there
hello_there

Reputation: 219

Pass state as props from parent to child and then update parent from child onSubmit

I have a parent component that renders a child component and passes it's initial state to the child component. I need a few clarifications- My gut feeling is to handle the event change in the Child component, is this correct? Upon submission, how do I pass the updated props back to parent? My gut is also telling that once the props is passed back to the parent, I can use componentDidUpdate() to set the state to be used elsewhere. If so, how?

class Parent extends React.Component {
  constructor() {
    super();

    this.state = {
      arrival: "arrival",
      departure: "destination"
    };
  }

  componentDidUpdate(){
      // how to update state?
  }

  render() {
    const { arrival, departure } = this.state;
    return <Child arrival={arrival} departure={departure} />;
  }
}



class Child extends React.Component{
  constructor(){
    this.handleSubmission = this.handleSubmission.bind(this);
  }


  handleSubmission(e){
    const target = e.target;
    const name = target.name;
    const value = target.value;

    // not sure how to handle props from here
   }


  render(){
    let { arrival, departure } = this.props;
    return(
      <form onSubmit = {this.handleSubmission} >
        <div class="form-group">
          <label for="departure">Departure</label>
          <input type="" class="form-control" name="departure" aria-describedby="emailHelp" placeholder="Enter Departing Station"/>
        </div>
        <div class="form-group">
          <label for="arrival">Arrival</label>
          <input type="password" class="form-control" name="arrival" id="inputArrival" placeholder="Enter Arriving Station"/>
        </div>
        <button type="submit" class="btn btn-primary">Submit</button>
      </form>
    )
  }
}

export default Child

Upvotes: 2

Views: 458

Answers (2)

dance2die
dance2die

Reputation: 36895

@hello_there

Please disregard the previous answer, as it requires a lot more than just changing a few props.

I've forked the Sandbox and rewrote it here (so you can follow along).
Edit so.answer.57279072

I've outlined the steps to make the change to propagate to the parent below.

Capture the states of <input />

First step is to capture the state of form fields.
There are two ways to handle form fields

  1. Controlled Components
  2. Uncontrolled Components - discouraged

I am going to use the former (controlled) to capture the form field states by adding a state to Form component.

And you need to set the value={...} of each state and update each state from onChange event (using handleInputChange added below) for each form field.

I've added 👇 where changes were made

import React, { Component } from "react";

class Form extends Component {
  // .. 👇
  state = {
    departure: "",
    arrival: ""
  };

  //... rest removed for brevity

  // .. 👇 is used to update each form field state.
  handleInputChange = e => {
    e.preventDefault();
    const { name, value } = e.target;
    this.setState({ [name]: value });
  };


  render() {
    const { departure, arrival } = this.state;

    return (
      <form onSubmit={this.handleSubmission}>
        <div className="form-group">
          <label> Departure</label>
          <input
            className="form-control"
            name="departure"
            placeholder="Enter Departing Station"
            // ... 👇         ...👇
            value={departure} onChange={this.handleInputChange}
          />
        </div>
        <div className="form-group">
          <label> Arrival</label>
          <input
            className="form-control"
            name="arrival"
            id="inputArrival"
            placeholder="Enter Arriving Station"
            // ... 👇        ...👇
            value={arrival} onChange={this.handleInputChange}
          />
        </div>
        <button type="submit" className="btn btn-primary">
          Submit
        </button>
      </form>
    );
  }
}

Update App's state change event handler

Now we have states handy, we need to update the App's updateState to accept a whole new state, so we don't make multiple method calls (this.props.updateParentState) and it'd let us pass a new reference so that React would know that the state has been changed in the App component.


class App extends Component {
  constructor() {
    super();

    this.state = {
      arrival: "arrival",
      departure: "departure"
    };
  }

  // From this 👇
  // updateState = (name, value) => {
  //   this.setState({
  //     [name]: value
  //   });
  // };

  // to this 👇
  updateState = newState => this.setState(newState);

  componentDidUpdate(prevProps, prevState) {
    const { arrival, departure } = this.state;
    console.log(`Arrival: ${arrival}, departure: ${departure}`);
  }

  render() {
    const { arrival, departure } = this.state;
    return (
      <Fragment>
        <Form
          arrival={arrival}
          departure={departure}
          // 👇 stays the same
          updateParentState={this.updateState}
        />
      </Fragment>
    );
  }
}

Update Child's submission event handler

Now the App.updateState accepts a state object, which can be used to update App.state, let's change Child.handSubmission.

  handleSubmission = e => {
    e.preventDefault();
    // this.props.updateParentState(name, value);
    this.props.updateParentState(this.state);
  };

You can see that this.props.updateParentState(name, value) has been replaced with this.props.updateParentState(this.state), which would let us update App.state at once.

Now you should be able to see the change in the App.componentDidUpdate.

working demo





OLD ANSWER - Disregard this

Changing the state in the child doesn't cause the re-render in the parent (so componentDidUpdate is probably not triggered by change in child component). So what you can do is to pass the event handler down to the child, which the child can notify the Parent component that something has changed.

I've explained the flow of how you can update the parent's states.

First you need to create a handler, with which you can update the Parent's state with.

updateState = (name, value) => this.setState({ [name]: value });

Here, I am using [name], which is a dynamic property. So it should match up with the Parent's state name. In this case either arrival or departure.

Then you need to pass that event handler to the Child (you can name the prop name to whatever (In the code below, I used updateParentState but it can be updateWhatever so long as you pass it the updateState correctly).

<Child arrival={arrival} departure={departure} updateParentState={updateState} />

Here is the complete code that will update the parent state. Changes are indicated with "👇" emoji below.

class Parent extends React.Component {
  constructor() {
    super();

    this.state = {
      arrival: "arrival",
      departure: "destination"
    };
  }

  // This is what you pass to the child as a `prop` to call.
  updateState = (name, value) => this.setState({ [name]: value });


  render() {
    const { arrival, departure } = this.state;
    // pass the event handler to the child component             ... 👇 ...
    return <Child arrival={arrival} departure={departure} updateParentState={updateState} />;
  }
}

class Child extends React.Component{
  constructor(){
    this.handleSubmission = this.handleSubmission.bind(this);
  }


  handleSubmission(e){
    const target = e.target;
    const name = target.name;
    const value = target.value;

    // 👇 Update the "Parent"'s state.
    // `name` & `value` will be the supplied to `Parent.updateState`
    this.props.updateParentState(name, value)
   }


  render(){
    // ... removed for brevity
  }
}

If the statement management gets too hard to manage with prop-drilling, then you can reach out for Context API (after familiarizing with it, you can check out How to use React Context effectively, which uses Hooks). Or you can use Redux.

Upvotes: 1

Dupocas
Dupocas

Reputation: 21307

Just pass to your children a reference on how to update the state:

class Parent extends React.Component{
    state = {departure : ''}

   setDeparture = departure => this.setState({ departure })

   render(){ <Child setDeparture={this.setDeparture} />  }
}

const Child = ({setDeparture}) => <button onClick={() => setDeparture('foo')}>Click</button>

Upvotes: 1

Related Questions