How to access child’s state in React?

How to access child’s state in React?

I have the following structure:
FormEditor – holds multiple FieldEditor
FieldEditor – edits a field of the form and saving various values about it in it’s state
When a button is clicked within FormEditor, I want to be able to collect information about the fields from all FieldEditor components, information that’s in their state, and have it all within FormEditor.
I considered storing the information about the fields outside of FieldEditor’s state and put it in FormEditor’s state instead. However, that would require FormEditor to listen to each of it’s FieldEditor components as they change and store their information in it’s state.
Can’t I just access the children’s state instead? Is it ideal?

Solutions/Answers:

Solution 1:

If you already have onChange handler for the individual FieldEditors I don’t see why you couldn’t just move the state up to the FormEditor component and just pass down a callback from there to the FieldEditors that will update the parent state. That seems like a more React-y way to do it, to me.

Something along the line of this perhaps:

class FieldEditor extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    const text = event.target.value;
    this.props.onChange(this.props.id, text);
  }

  render() {
    return (
      <div className="field-editor">
        <input onChange={this.handleChange} value={this.props.value} />
      </div>
    );
  }
}

class FormEditor extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};

    this.handleFieldChange = this.handleFieldChange.bind(this);
  }

  handleFieldChange(fieldId, value) {
    this.setState({ [fieldId]: value });
  }

  render() {
    const fields = this.props.fields.map(field => (
      <FieldEditor
        key={field}
        id={field}
        onChange={this.handleFieldChange}
        value={this.state[field]}
      />
    ));

    return (
      <div>
        {fields}
        <div>{JSON.stringify(this.state)}</div>
      </div>
    );
  }
}

// Convert to class component and add ability to dynamically add/remove fields by having it in state
const App = () => {
  const fields = ["field1", "field2", "anotherField"];

  return <FormEditor fields={fields} />;
};

ReactDOM.render(<App />, document.body);

http://jsbin.com/qeyoxobixa/edit?js,output

Related:  Javascript - check array for value [duplicate]

Edit: just for the heck of it I’ve re-written the above example using hooks for anyone that’s interested:

const FieldEditor = ({ value, onChange, id }) => {
  const handleChange = event => {
    const text = event.target.value;
    onChange(id, text);
  };

  return (
    <div className="field-editor">
      <input onChange={handleChange} value={value} />
    </div>
  );
};

const FormEditor = props => {
  const [values, setValues] = useState({});
  const handleFieldChange = (fieldId, value) => {
    setValues({ ...values, [fieldId]: value });
  };
  const fields = props.fields.map(field => (
    <FieldEditor
      key={field}
      id={field}
      onChange={handleFieldChange}
      value={values[field]}
    />
  ));

  return (
    <div>
      {fields}
      <pre>{JSON.stringify(values, null, 2)}</pre>
    </div>
  );
};

// To add abillity to dynamically add/remove fields keep the list in state
const App = () => {
  const fields = ["field1", "field2", "anotherField"];

  return <FormEditor fields={fields} />;
};

Solution 2:

Just before I go into detail about how you can access the state of a child component, please make sure to read Markus-ipse‘s answer regarding a better solution to handle this particular scenario.

If you do indeed wish to access the state of a component’s children, you can assign a property called ref to each child. There are now two ways to implement references: Using React.createRef() and callback refs.

Using React.createRef()

This is currently the recommended way to use references as of React 16.3 (See the docs for more info). If you’re using an earlier version then see below regarding callback references.

Related:  C3JS - Cannot read property 'category10' of undefined

You’ll need to create a new reference in the constructor of your parent component and then assign it to a child via the ref attribute.

class FormEditor extends React.Component {
  constructor(props) {
    super(props);
    this.FieldEditor1 = React.createRef();
  }
  render() {
    return <FieldEditor ref={this.FieldEditor1} />;
  }
}

In order to access this kind of ref, you’ll need to use:

const currentFieldEditor1 = this.FieldEditor1.current;

This will return an instance of the mounted component so you can then use currentFieldEditor1.state to access the state.

Just a quick note to say that if you use these references on a DOM node instead of a component (e.g. <div ref={this.divRef} />) then this.divRef.current will return the underlying DOM element instead of a component instance.

Callback Refs

This property takes a callback function that is passed a reference to the attached component. This callback is executed immediately after the component is mounted or unmounted.

For example:

<FieldEditor
    ref={(fieldEditor1) => {this.fieldEditor1 = fieldEditor1;}
    {...props}
/>

In these examples the reference is stored on the parent component. To call this component in your code, you can use:

this.fieldEditor1

and then use this.fieldEditor1.state to get the state.

One thing to note, make sure your child component has rendered before you try to access it ^_^

As above, if you use these references on a DOM node instead of a component (e.g. <div ref={(divRef) => {this.myDiv = divRef;}} />) then this.divRef will return the underlying DOM element instead of a component instance.

Further Information

If you want to read more about React’s ref property, check out this page from Facebook.

Related:  How to split and modify a string in NodeJS?

Make sure you read the “Don’t Overuse Refs” section that says that you shouldn’t use the child’s state to “make things happen”.

Hope this helps ^_^

Edit: Added React.createRef() method for creating refs. Removed ES5 code.

Solution 3:

Now You can access the InputField’s state which is the child of FormEditor .

Basically whenever there is a change in the state of the input field(child) we are getting the value from the event object and then passing this value to the Parent where in the state in the Parent is set.

On button click we are just printing the state of the Input fields.

The key point here is that we are using the props to get the Input Field’s id/value and also to call the functions which are set as attributes on the Input Field while we generate the reusable child Input fields.

class InputField extends React.Component{
  handleChange = (event)=> {
    const val = event.target.value;
    this.props.onChange(this.props.id , val);
  }

  render() {
    return(
      <div>
        <input type="text" onChange={this.handleChange} value={this.props.value}/>
        <br/><br/>
      </div>
    );
  }
}       


class FormEditorParent extends React.Component {
  state = {};
  handleFieldChange = (inputFieldId , inputFieldValue) => {
    this.setState({[inputFieldId]:inputFieldValue});
  }
  //on Button click simply get the state of the input field
  handleClick = ()=>{
    console.log(JSON.stringify(this.state));
  }

  render() {
    const fields = this.props.fields.map(field => (
      <InputField
        key={field}
        id={field}
        onChange={this.handleFieldChange}
        value={this.state[field]}
      />
    ));

    return (
      <div>
        <div>
          <button onClick={this.handleClick}>Click Me</button>
        </div>
        <div>
          {fields}
        </div>
      </div>
    );
  }
}

const App = () => {
  const fields = ["field1", "field2", "anotherField"];
  return <FormEditorParent fields={fields} />;
};

ReactDOM.render(<App/>, mountNode);