3

I have 3 components in React, one acts as a container which passes down my child components to be rendered in a form. When the form is submitted I want to get each of the child components in my parent component, loop through each one, create an object my server expects then send the list of objects back to the server. I am struggling to access the child components in my onSubmit function in my parent component.

Here is my parent component

ParentFixturesComponent.js

class ParentFixturesComponent extends Component {

    constructor() {
        super();
        this.state = {
            numChildren: 0,
        };
    }

    onAddMatch() {
        this.setState({
            numChildren: this.state.numChildren + 1
        });
    }

    onSubmit(e) {
        e.preventDefault();
        // loop through the child components
        // create a match object with them
        // var match = {
        //     id: uuid(),
        //     title: uuid(),
        //     start: e.something,
        //     end: e.something,
        // };
        console.log("submit works");
    }

    render() {
        const children = [];
        for (let i = 0; i < this.state.numChildren; i += 1) {
            children.push(<SingleMatchForm key={uuid()}/>)
        }

        return (
            <Fixtures addMatch={this.onAddMatch.bind(this)} save={this.onSubmit.bind(this)} >
                {children}
            </Fixtures>
        );
    }

}

export default ParentFixturesComponent;

Component which holds my form where my parent renders all my children components

ChildFixturesContainer.js

class Fixtures extends Component {


    constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(name) {
        this.console.log(this.props);
    }

    render() {
        return (
            <div className="tray tray-center">
                <div className="row">
                    <div className="col-md-8">
                        <div className="panel mb25 mt5">
                            <div className="panel-heading">
                                <span className="panel-title">Fixtures</span>
                            </div>
                            <div className="panel-body p20 pb10">
                                <div id="fixture-parent" onChange={this.handleChange.bind(this)}>
                                    {this.props.children}
                                </div>
                            </div>
                            <div className="section-divider mb40" id="spy1"> </div>
                            <button className="btn btn-primary tm-tag" onClick={this.props.addMatch}>Add Match</button>
                            <button className="btn btn-alert tm-tag" onClick={this.props.save}>Save</button>
                        </div>
                    </div>
                </div>
            </div>
        );
    }

}

export default Fixtures;

And finally my child individual form component.

SingleMatchComponent.js

class SingleMatchForm extends Component {

    constructor() {
        super();
        this.state = {
            startDate: moment()
        };
    }

    handleChange(date) {
        this.setState({
            startDate: date
        });
    }

    render() {
        return (
            <div className="row">
                <div key={this.props.id} className="form-group">
                    <label className="control-label col-md-2">New Match</label>
                    <div className="col-md-6">
                        <DatePicker
                            selected={this.state.startDate}
                            onChange={this.handleChange.bind(this)}/>
                        <div className="section-divider mb40" id="spy1"> </div>
                    </div>
                </div>
            </div>
        );
    }

}

export default SingleMatchForm;
N P
  • 2,319
  • 7
  • 32
  • 54
  • Hi Nick, you should pass functions as props from parent to child, which act as callbacks. So when onSubmit() function is called you will have all data in your parent components state. For sample code refer to this thread http://stackoverflow.com/questions/38394015/how-to-pass-data-from-child-component-to-its-parent-in-reactjs – Rishabh Bhatia May 13 '17 at 13:39

2 Answers2

13

Define a function in your parent and send it to the child, call the parent function in child component using props. Something like:

Parent

class ParentFixturesComponent extends Component {

    // don't forget to bind the function in parent
    constructor() {
        super();
        this.state = {
            numChildren: 0,
        };
        this.someFunctionHere = this.someFunctionHere.bind(this);
    }

    someFunctionHere(param) {
        // do whatever you need
    }

    render() {
        const children = [];
        for (let i = 0; i < this.state.numChildren; i += 1) {
            children.push(<SingleMatchForm key={uuid()}/>)
        }

        return (
            <Fixtures
                addMatch={this.onAddMatch.bind(this)}
                save={this.onSubmit.bind(this)}
                someFunctionHere={this.someFunctionHere}
            >
                {children}
            </Fixtures>
        );
    }
}

export default ParentFixturesComponent;

Child

class Fixtures extends Component {

    // constructor and other stuff...

    childFunctionHere() {
        this.props.someFunctionHere(params);            
    }

    render() {
        return (
            <div id="fixture-parent" onChange={this.childFunctionHere}>
        );
    }
}

export default Fixtures;
errata
  • 5,695
  • 10
  • 54
  • 99
  • 1
    This worked for me, but I had to bind the 'childFunctionHere' function in the child constructor first. – x7BiT Nov 04 '17 at 15:28
  • Do not use `uuid()` to generate keys for elements in react render(). This will create new keys every time render() is run and cause a lot more dom manipulation than necessary. Keep keys consistent across render, in this case with `i`. – Samuel Neff Feb 19 '18 at 00:10
5

Essentially, you're asking about the difference between props and state.

There are two types of data that control a component: props and state. props are set by the parent and they are fixed throughout the lifetime of a component. For data that is going to change, we have to use state. Docs

This also addresses smart/dumb (or logic/display or container/presenter) components.

Your parent component should hold all of the state and the logic for changing it, then pass everything down to the other components as props. Your child components shouldn't really even have a state they just handle displaying it and, when something happens, they just send that call back up to the parent component to do whatever needs to be done.

When the child components call those handlers via props, they'll execute in the parent, updating the state there. Then you can just collect the state in the parent and submit it.

class TheParent extends component {

  constructor() {
    super();
    this.state = {
      someState: 0,
      someMoreState: 1,
      evenMoreState: 2
    };
    autoBind(this) // <- use autobind so you don't have to list them all out
  }

  updateSomeState() {
        // do something
  }

  updateSomeMoreState() {
        // do something
  }

  updateEvenMoreState() {
        // do something
  }

  onSubmit() {
    // get state and submit
  }

  render () {
    return (
      <Component1 
        someProp={this.state.someState} 
        handler={this.updateSomeState} 
      />
      <Component2 
        someProp={this.state.someMoreState} 
        handler={this.updateSomeMoreState} 
      />          
      <Component2 
        someProp={this.state.evenMoreState} 
        handler={this.updateEvenMoreState} 
      />
    )
  }

}
Will Luce
  • 1,781
  • 3
  • 20
  • 33