314

I have the following structure:

FormEditor - holds multiple instances of FieldEditor FieldEditor - edits a field of the form and saving various values about it in its 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 its FieldEditor components as they change and store their information in its state.

Can't I just access the children's state instead? Is it ideal?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Zippo
  • 15,850
  • 10
  • 60
  • 58
  • 5
    *"Can't I just access the children's state instead? Is it ideal?"* No. State is something internal and should not leak to the outside. You could implement accessor methods for your component, but even that is not ideal. – Felix Kling Jan 09 '15 at 16:55
  • @FelixKling Then you're suggesting that the ideal way for child to parent communication is only events? – Zippo Jan 09 '15 at 18:37
  • 3
    Yes, events is one way. Or have a one directional data flow like Flux promotes: https://facebook.github.io/flux/ – Felix Kling Jan 09 '15 at 18:40
  • 1
    If you are not going to use `FieldEditor`s separately, saving their state in `FormEditor` sounds good. If this is the case, your `FieldEditor` instances will render based on `props` passed by their form editor, not their `state`. A more complex but flexible way would be to make a serializer that goes through any container children and finds all `FormEditor` instances among them and serializes them into a JSON object. The JSON object can be optionally nested (more than one level) based on the instances' nesting levels in the form editor. – Meglio Jan 24 '16 at 13:03
  • 8
    I think the React Docs ['Lifting State Up'](https://facebook.github.io/react/docs/lifting-state-up.html) is probably the most 'Reacty' way of doing this – icc97 May 11 '17 at 14:08

6 Answers6

220

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 documentation for more information). If you're using an earlier version then see below regarding callback references.

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.

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".

maruk0chan
  • 43
  • 8
Martoid Prime
  • 2,433
  • 2
  • 11
  • 11
  • 5
    Also neat little tidbit, you can call functions from `this.refs.yourComponentNameHere`. I've found it useful for changing state via functions. Example: `this.refs.textInputField.clearInput();` – Dylan Pierce Sep 09 '15 at 14:19
  • 9
    Beware (from the docs): *Refs are a great way to send a message to a particular child instance in a way that would be inconvenient to do via streaming Reactive `props` and `state`. They should, however, not be your go-to abstraction for flowing data through your application. By default, use the Reactive data flow and save `ref`s for use cases that are inherently non-reactive.* – Lynn Jul 20 '16 at 12:28
  • 2
    I only get state that was defined inside constructor (not up to date state) – Vlado Pandžić Oct 21 '16 at 11:12
  • 3
    Using refs now is classed as using '[Uncontrolled Components](https://facebook.github.io/react/docs/uncontrolled-components.html)' with the recommendation to use 'Controlled Components' – icc97 May 11 '17 at 23:03
  • Is it possible to do this if the child component is a Redux `connected` component? – jmancherje Mar 20 '18 at 19:50
  • if you are using connect way, u must pass props for example: connect(null, mapDispatchToProps, null, {forwardRef: true}) – DonnaTelloo Apr 08 '21 at 10:34
188

If you already have an 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:

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 the ability to dynamically add/remove fields, keep the list in state
const App = () => {
  const fields = ["field1", "field2", "anotherField"];

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

Original - pre-hooks version:

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 a class component and add the 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);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Markus-ipse
  • 7,196
  • 4
  • 29
  • 34
  • 3
    This is useful for onChange, but not as much in onSubmit. – 190290000 Ruble Man Jul 26 '17 at 13:21
  • 2
    @190290000RubleMan I'm not sure what you mean, but since the onChange handlers on the individual fields make sure that you always have the complete form data available in your state in your FormEditor component, your onSubmit would just include something like `myAPI.SaveStuff(this.state);`. If you elaborate a bit more, maybe I can give you a better answer :) – Markus-ipse Jul 26 '17 at 13:31
  • 3
    Say I have a form with 3 fields of different type, each with its own validation. I'd like to validate each of them before submitting. If validation fails, each field that has an error should rerender. I haven't figured out how to do this with your proposed solution, but perhaps there is a way! – 190290000 Ruble Man Jul 26 '17 at 13:40
  • 1
    @190290000RubleMan Seems like a different case than the original question. Open a new question with more details and perhaps some sample code, and I can have a look later :) – Markus-ipse Jul 26 '17 at 13:53
  • 1
    You might find [this answer useful](https://stackoverflow.com/questions/24147331/react-the-right-way-to-pass-form-element-state-to-sibling-parent-elements/58965261#58965261): it uses state hooks, it covers where the state should be created, how to avoid form re-render on every keystroke, how to do field validation, etc. – Andrew Nov 21 '19 at 00:21
  • 2
    This will lead to re render whole parent component, isnt it? – lakmal_sathyajith Oct 19 '20 at 17:20
48

As the previous answers said, try to move the state to a top component and modify the state through callbacks passed to its children.

In case that you really need to access to a child state that is declared as a functional component (hooks) you can declare a ref in the parent component, and then pass it as a ref attribute to the child, but you need to use React.forwardRef and then the hook useImperativeHandle to declare a function you can call in the parent component.

Take a look at the following example:

const Parent = () => {
    const myRef = useRef();
    return <Child ref={myRef} />;
}

const Child = React.forwardRef((props, ref) => {
    const [myState, setMyState] = useState('This is my state!');
    useImperativeHandle(ref, () => ({getMyState: () => {return myState}}), [myState]);
})

Then you should be able to get myState in the Parent component by calling:

myRef.current.getMyState();

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 1
    This is fantastic. I dislike almost every Child to Parent state sharing pattern, this is great. I don't know how much longer React can recommend classes over hooks. Hooks are very fast. – starpebble Sep 03 '20 at 20:18
  • When I call `ref.current.getMyState()` within the DOM, this raises an error `ref.current.getMyState is not a function`, because the component was not built yet. So if one needs the state for building the DOM, I'm afraid one has to pick another solution. – Sebastian Jan 31 '21 at 12:36
  • Hy @Sebastian since component is not mounted in dom, and you're trying to access undefined.getMyState() that's why it's returing not a function typeerror, you can access like ref.current?.getMyState() – Aman Jha Jan 05 '23 at 07:26
30

It's 2020 and lots of you will come here looking for a similar solution but with Hooks (they are great!) and with the latest approaches in terms of code cleanliness and syntax.

So as previous answers had stated, the best approach to this kind of problem is to hold the state outside of child component fieldEditor. You could do that in multiple ways.

The most "complex" is with a global context (state) that both parent and children could access and modify. It's a great solution when components are very deep in the tree hierarchy and so it's costly to send props in each level.

In this case I think it's not worth it, and a more simple approach will bring us the results we want, just using the powerful React.useState().

An approach with a React.useState() hook - way simpler than with Class components

As said, we will deal with changes and store the data of our child component fieldEditor in our parent fieldForm. To do that we will send a reference to the function that will deal and apply the changes to the fieldForm state, you could do that with:

function FieldForm({ fields }) {
  const [fieldsValues, setFieldsValues] = React.useState({});
  const handleChange = (event, fieldId) => {
    let newFields = { ...fieldsValues };
    newFields[fieldId] = event.target.value;

    setFieldsValues(newFields);
  };

  return (
    <div>
      {fields.map(field => (
        <FieldEditor
          key={field}
          id={field}
          handleChange={handleChange}
          value={fieldsValues[field]}
        />
      ))}
      <div>{JSON.stringify(fieldsValues)}</div>
    </div>
  );
}

Note that React.useState({}) will return an array with position 0 being the value specified on call (Empty object in this case), and position 1 being the reference to the function that modifies the value.

Now with the child component, FieldEditor, you don't even need to create a function with a return statement. A lean constant with an arrow function will do!

const FieldEditor = ({ id, value, handleChange }) => (
  <div className="field-editor">
    <input onChange={event => handleChange(event, id)} value={value} />
  </div>
);

Aaaaand we are done, nothing more. With just these two slim functional components we have our end goal "access" our child FieldEditor value and show it off in our parent.

You could check the accepted answer from 5 years ago and see how Hooks made React code leaner (by a lot!).

Hope my answer helps you learn and understand more about Hooks, and if you want to check a working example here it is.

Michael Fulton
  • 4,608
  • 3
  • 25
  • 41
Josep Vidal
  • 2,580
  • 2
  • 16
  • 27
6

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 a 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 a 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);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Dhana
  • 694
  • 13
  • 18
  • 7
    Not sure I can upvote this one. What do you mention here that isn't already answered by @Markus-ipse ? – Alexander Bird Nov 30 '18 at 23:06
  • OP provided the syntax using classes instead of functions( as given in the best answers) which is impt since most ppl reading this question would also not be sure the correct syntax if i were to convert the best answer in this question to use classes. Sorry but i don't even know the name of class/function thingy – Peter Jan 13 '21 at 07:20
  • 1
    Also I would think more variations of the same solution is good for people who are brute forcing their way through react :D – Peter Jan 13 '21 at 07:25
3

You may access the child state by passing a callback to the child component.

const Parent = () => {
  return (
    <Child onSubmit={(arg) => { 
             console.log('accessing child state from parent callback: ', arg) 
           }} 
    /> 
  )
}

const Child = ({onSubmit}) => {
    const [text, setText] = useState('');

    return (
      <>
        <input value={text} onChange={setText}>
        <button onClick={() => onSubmit(text)} />
      </>
    )
}

Now if you click the button in the child component, you will execute the function passed from the parent and have access to the child component's state variables.

Ayudh
  • 1,673
  • 1
  • 22
  • 55