213
  • Suppose I have a React class P, which renders two child classes, C1 and C2.
  • C1 contains an input field. I'll refer to this input field as Foo.
  • My goal is to let C2 react to changes in Foo.

I've come up with two solutions, but neither of them feels quite right.

First solution:

  1. Assign P a state, state.input.
  2. Create an onChange function in P, which takes in an event and sets state.input.
  3. Pass this onChange to C1 as a props, and let C1 bind this.props.onChange to the onChange of Foo.

This works. Whenever the value of Foo changes, it triggers a setState in P, so P will have the input to pass to C2.

But it doesn't feel quite right for the same reason: I'm setting the state of a parent element from a child element. This seems to betray the design principle of React: single-direction data flow.
Is this how I'm supposed to do it, or is there a more React-natural solution?

Second solution:

Just put Foo in P.

But is this a design principle I should follow when I structure my app—putting all form elements in the render of the highest-level class?

Like in my example, if I have a large rendering of C1, I really don't want to put the whole render of C1 to render of P just because C1 has a form element.

How should I do it?

octref
  • 6,521
  • 7
  • 28
  • 44

10 Answers10

209

So, if I'm understanding you correctly, your first solution is suggesting that you're keeping state in your root component? I can't speak for the creators of React, but generally, I find this to be a proper solution.

Maintaining state is one of the reasons (at least I think) that React was created. If you've ever implemented your own state pattern client side for dealing with a dynamic UI that has a lot of interdependent moving pieces, then you'll love React, because it alleviates a lot of this state management pain.

By keeping state further up in the hierarchy, and updating it through eventing, your data flow is still pretty much unidirectional, you're just responding to events in the Root component, you're not really getting the data there via two way binding, you're telling the Root component that "hey, something happened down here, check out the values" or you're passing the state of some data in the child component up in order to update the state. You changed the state in C1, and you want C2 to be aware of it, so, by updating the state in the Root component and re-rendering, C2's props are now in sync since the state was updated in the Root component and passed along.

class Example extends React.Component {
  constructor (props) {
    super(props)
    this.state = { data: 'test' }
  }
  render () {
    return (
      <div>
        <C1 onUpdate={this.onUpdate.bind(this)}/>
        <C2 data={this.state.data}/>
      </div>
    )
  }
  onUpdate (data) { this.setState({ data }) }
}

class C1 extends React.Component {
    render () {
      return (
        <div>
          <input type='text' ref='myInput'/>
          <input type='button' onClick={this.update.bind(this)} value='Update C2'/>
        </div>
      )
    }
    update () {
      this.props.onUpdate(this.refs.myInput.getDOMNode().value)
    }
})

class C2 extends React.Component {
    render () {
      return <div>{this.props.data}</div>
    }
})

ReactDOM.renderComponent(<Example/>, document.body)
captray
  • 3,618
  • 1
  • 18
  • 15
  • 6
    No problem. I actually went back after writing this post and reread some of the documentation, and it seems to be inline with their thinking and best practices. React has truly excellent documentation, and every time I end up wondering where something should go, they typically have it covered somewhere in the docs. Check out the section on state here, http://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html#what-components-should-have-state – captray Jun 11 '14 at 15:31
  • @captray but what about, if `C2` have a `getInitialState` for data and inside the `render` it uses `this.state.data`? – Dmitry Polushkin Dec 28 '14 at 18:25
  • 2
    @DmitryPolushkin If I'm understanding your question correctly, you want to pass data from your Root Component to C2 as props. In C2, that data would be set as initial state (i.e. getInitialState: function() { return { someData: this.props.dataFromParentThatWillChange } } and you'll want to implement componentWillReceiveProps and call this.setState with the new props to update the state in C2. Since I initially answered, I've been using Flux, and would highly recommend that you look at it as well. It makes your components cleaner and will change the way you think about state. – captray Dec 30 '14 at 21:22
  • 3
    @DmitryPolushkin I wanted to post this to followup. http://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html It's ok as long as you know what you're doing and you know that data is going to change, but in many situations you can probably move things around. It's also important to note that you don't have to build this as a hierarchy. You could mount C1 and C2 at different places in the DOM, and they can both listen to change events on some data. I see a lot of people pushing for hierarchical components when they don't need them. – captray Dec 31 '14 at 12:49
  • Everything looks fine and beautiful if we have only a few components, but let's say my UI goes down many levels, and in component C7 I have a button to change the "root" state or C1 state...well, passing the handler down each single level of the application seems tedious to me...but I guess this is what Redux and Flux are for, no? – Paranoid Android Nov 11 '16 at 13:12
  • Correct. You want to keep View State and Application State separate, although some fields may move back and forth, just depends on needs. In a Redux mindset, you keep all your Application State in your Store and your lower level components will pass messages to update the state in the Store. This alleviates the passing of handlers across many levels and data will continue to flow in one direction. – captray Nov 19 '16 at 23:08
  • Is it really necessary to use `refs` here? What about set the input value into C1's state? – Bruce Sun Jan 06 '17 at 02:03
  • 5
    The above code has 2 bugs - both of which involve not binding "this" in the right context, I have made the corrections above and also for anyone who needs a codepen demonstration: https://codepen.io/anon/pen/wJrRpZ?editors=0011 – Alex Mar 16 '17 at 08:56
  • @AlexH I've applied your edits. There were issues in the existing example. This code has changed many times since my original example (which was written in something like 0.9.0 o_O). Time flies. Regardless, if you're using ES7/Babel, you can actually get away with using arrow functions instead of .bind(). I don't like the bind syntax everywhere and the arrow functions look a lot cleaner. Just too busy to make the changes right now. If anyone is up for that, have at it. – captray Mar 22 '17 at 12:15
  • I had to use "this.props.onUpdate(this.refs.myInput.value)" instead of "this.props.onUpdate(this.refs.myInput.getDOMNode().value)" ... thanks for the great lead – user890255 Aug 30 '17 at 21:18
  • This is called "lifting state up": https://reactjs.org/docs/lifting-state-up.html – Jake Reece Aug 05 '19 at 00:07
  • My answer includes the example with `React.useState` hook. – Andrew Nov 21 '19 at 00:07
37

Having used React to build an app now, I'd like to share some thoughts to this question I asked half a year ago.

I recommend you to read

The first post is extremely helpful to understanding how you should structure your React app.

Flux answers the question why should you structure your React app this way (as opposed to how to structure it). React is only 50% of the system, and with Flux you get to see the whole picture and see how they constitute a coherent system.

Back to the question.

As for my first solution, it is totally OK to let the handler go the reverse direction, as the data is still going single-direction.

However, whether letting a handler trigger a setState in P can be right or wrong depending on your situation.

If the app is a simple Markdown converter, C1 being the raw input and C2 being the HTML output, it's OK to let C1 trigger a setState in P, but some might argue this is not the recommended way to do it.

However, if the app is a todo list, C1 being the input for creating a new todo, C2 the todo list in HTML, you probably want to handler to go two level up than P -- to the dispatcher, which let the store update the data store, which then send the data to P and populate the views. See that Flux article. Here is an example: Flux - TodoMVC

Generally, I prefer the way described in the todo list example. The less state you have in your app the better.

octref
  • 6,521
  • 7
  • 28
  • 44
  • 7
    I frequently discuss this in presentations on both React and Flux. The point I try to stress is what you've described above, and that is the separation of View State and Application State. There are situations where things can transition from just being View State to becoming Application State, especially in situations where you're saving the state of the UI (like preset values). I think it's awesome that you came back with your own thoughts a year later. +1 – captray Aug 11 '15 at 01:57
  • @captray So can we say that redux is powerful than react in all circumstance? Despite their learning curve... (coming from Java) – Bruce Sun Jan 06 '17 at 02:03
  • I'm not sure what you mean. They handle two different things very well, and are a proper separation of concerns. – captray Jan 06 '17 at 14:21
  • 1
    We have a project using redux, and months into it, it seems as if actions are used for everything. It's like this big mix of spaghetti code and impossible to understand, total disaster. State management might be good, but possibly severely misunderstood. Is it safe to assume that flux/redux should only be used for parts of state that need to be accessed globally? – wayofthefuture Feb 14 '17 at 17:49
  • 2
    @wayofthefuture Without seeing your code, I can say that I see a lot of spaghetti React just like good ole spaghetti jQuery. The best advice I can offer is to try and follow SRP. Make your components as simple as possible; dumb rendering components if you can. I also push for abstractions like a component. It does one thing well. Provide data. This usually becomes the 'root' component and passes data down by leveraging children (and cloning) as props (defined contract). Ultimately, try to think on the Server first. It'll make your JS much cleaner with better data structures. – captray Apr 07 '17 at 02:13
10

Five years later with introduction of React Hooks there is now much more elegant way of doing it with use useContext hook.

You define context in a global scope, export variables, objects and functions in the parent component and then wrap children in the App in a context provided and import whatever you need in child components. Below is a proof of concept.

import React, { useState, useContext } from "react";
import ReactDOM from "react-dom";
import styles from "./styles.css";

// Create context container in a global scope so it can be visible by every component
const ContextContainer = React.createContext(null);

const initialAppState = {
  selected: "Nothing"
};

function App() {
  // The app has a state variable and update handler
  const [appState, updateAppState] = useState(initialAppState);

  return (
    <div>
      <h1>Passing state between components</h1>

      {/* 
          This is a context provider. We wrap in it any children that might want to access
          App's variables.
          In 'value' you can pass as many objects, functions as you want. 
           We wanna share appState and its handler with child components,           
       */}
      <ContextContainer.Provider value={{ appState, updateAppState }}>
        {/* Here we load some child components */}
        <Book title="GoT" price="10" />
        <DebugNotice />
      </ContextContainer.Provider>
    </div>
  );
}

// Child component Book
function Book(props) {
  // Inside the child component you can import whatever the context provider allows.
  // Earlier we passed value={{ appState, updateAppState }}
  // In this child we need the appState and the update handler
  const { appState, updateAppState } = useContext(ContextContainer);

  function handleCommentChange(e) {
    //Here on button click we call updateAppState as we would normally do in the App
    // It adds/updates comment property with input value to the appState
    updateAppState({ ...appState, comment: e.target.value });
  }

  return (
    <div className="book">
      <h2>{props.title}</h2>
      <p>${props.price}</p>
      <input
        type="text"
        //Controlled Component. Value is reverse vound the value of the variable in state
        value={appState.comment}
        onChange={handleCommentChange}
      />
      <br />
      <button
        type="button"
        // Here on button click we call updateAppState as we would normally do in the app
        onClick={() => updateAppState({ ...appState, selected: props.title })}
      >
        Select This Book
      </button>
    </div>
  );
}

// Just another child component
function DebugNotice() {
  // Inside the child component you can import whatever the context provider allows.
  // Earlier we passed value={{ appState, updateAppState }}
  // but in this child we only need the appState to display its value
  const { appState } = useContext(ContextContainer);

  /* Here we pretty print the current state of the appState  */
  return (
    <div className="state">
      <h2>appState</h2>
      <pre>{JSON.stringify(appState, null, 2)}</pre>
    </div>
  );
}

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

You can run this example in the Code Sandbox editor.

Edit passing-state-with-context

J. Wrong
  • 822
  • 11
  • 10
  • 1
    how can we share `const ContextContainer = React.createContext(null);` with the child components so I can create separate files? – APorter1031 Sep 23 '20 at 20:51
  • @APorter1031 one way is to `export const ContextContainer = ...` and import it in the other file. – Qtax Sep 28 '22 at 11:19
5

The first solution, with keeping the state in parent component, is the correct one. However, for more complex problems, you should think about some state management library, redux is the most popular one used with react.

Nesha Zoric
  • 6,218
  • 42
  • 34
  • Agreed. My response was written back when most were writing things in "pure React". Prior to the fluxsplosion. – captray Jun 14 '18 at 18:17
5

I'm surprised that there are no answers with a straightforward idiomatic React solution at the moment I'm writing. So here's the one (compare the size and complexity to others):

class P extends React.Component {
    state = { foo : "" };

    render(){
        const { foo } = this.state;

        return (
            <div>
                <C1 value={ foo } onChange={ x => this.setState({ foo : x })} />
                <C2 value={ foo } />
            </div>
        )
    }
}

const C1 = ({ value, onChange }) => (
    <input type="text"
           value={ value }
           onChange={ e => onChange( e.target.value ) } />
);

const C2 = ({ value }) => (
    <div>Reacting on value change: { value }</div>
);

I'm setting the state of a parent element from a child element. This seems to betray the design principle of React: single-direction data flow.

Any controlled input (idiomatic way of working with forms in React) updates the parent state in its onChange callback and still doesn't betray anything.

Look carefully at C1 component, for instance. Do you see any significant difference in the way how C1 and built-in input component handle the state changes? You should not, because there is none. Lifting up the state and passing down value/onChange pairs is idiomatic for raw React. Not usage of refs, as some answers suggest.

gaperton
  • 3,566
  • 1
  • 20
  • 16
  • 1
    What version of react are you using? I'm getting experimental class property issues and foo is not defined. – Isaac Pak Feb 01 '19 at 19:32
  • That wasn't about a version of react. The code of the component was wrong. Fixed it, try now. – gaperton Feb 05 '19 at 20:46
  • Obviously, you have to extract state member from `this.state`, the return was missing in render, and multiple components must be wrapped in div or something. Don't know how I missed that when I wrote the original answer. Must have been the editing mistake. – gaperton Feb 05 '19 at 20:48
  • 2
    I like this solution. If anyone wants to tinker with it, here's a [sandbox](https://codesandbox.io/embed/zxl7n356np) for you. – Isaac Pak Feb 05 '19 at 22:33
4

More recent answer with an example, which uses React.useState

Keeping the state in the parent component is the recommended way. The parent needs to have an access to it as it manages it across two children components. Moving it to the global state, like the one managed by Redux, is not recommended for same same reason why global variable is worse than local in general in software engineering.

When the state is in the parent component, the child can mutate it if the parent gives the child value and onChange handler in props (sometimes it is called value link or state link pattern). Here is how you would do it with hooks:


function Parent() {
    var [state, setState] = React.useState('initial input value');
    return <>
        <Child1 value={state} onChange={(v) => setState(v)} />
        <Child2 value={state}>
    </>
}

function Child1(props) {
    return <input
        value={props.value}
        onChange={e => props.onChange(e.target.value)}
    />
}

function Child2(props) {
    return <p>Content of the state {props.value}</p>
}

The whole parent component will re-render on input change in the child, which might be not an issue if the parent component is small / fast to re-render. The re-render performance of the parent component still can be an issue in the general case (for example large forms). This is solved problem in your case (see below).

State link pattern and no parent re-render are easier to implement using the 3rd party library, like Hookstate - supercharged React.useState to cover variety of use cases, including your's one. (Disclaimer: I am an author of the project).

Here is how it would look like with Hookstate. Child1 will change the input, Child2 will react to it. Parent will hold the state but will not re-render on state change, only Child1 and Child2 will.

import { useStateLink } from '@hookstate/core';

function Parent() {
    var state = useStateLink('initial input value');
    return <>
        <Child1 state={state} />
        <Child2 state={state}>
    </>
}

function Child1(props) {
    // to avoid parent re-render use local state,
    // could use `props.state` instead of `state` below instead
    var state = useStateLink(props.state)
    return <input
        value={state.get()}
        onChange={e => state.set(e.target.value)}
    />
}

function Child2(props) {
    // to avoid parent re-render use local state,
    // could use `props.state` instead of `state` below instead
    var state = useStateLink(props.state)
    return <p>Content of the state {state.get()}</p>
}

PS: there are many more examples here covering similar and more complicated scenarios, including deeply nested data, state validation, global state with setState hook, etc. There is also complete sample application online, which uses the Hookstate and the technique explained above.

Andrew
  • 2,055
  • 2
  • 20
  • 27
1

You should learn Redux and ReactRedux library.It will structure your states and props in one store and you can access them later in your components .

Ramin Taghizada
  • 125
  • 2
  • 9
1

With React >= 16.3 you can use ref and forwardRef, to gain access to child's DOM from its parent. Don't use old way of refs anymore.
Here is the example using your case :

import React, { Component } from 'react';

export default class P extends React.Component {
   constructor (props) {
      super(props)
      this.state = {data: 'test' }
      this.onUpdate = this.onUpdate.bind(this)
      this.ref = React.createRef();
   }

   onUpdate(data) {
      this.setState({data : this.ref.current.value}) 
   }

   render () {
      return (
        <div>
           <C1 ref={this.ref} onUpdate={this.onUpdate}/>
           <C2 data={this.state.data}/>
        </div>
      )
   }
}

const C1 = React.forwardRef((props, ref) => (
    <div>
        <input type='text' ref={ref} onChange={props.onUpdate} />
    </div>
));

class C2 extends React.Component {
    render () {
       return <div>C2 reacts : {this.props.data}</div>
    }
}

See Refs and ForwardRef for detailed info about refs and forwardRef.

Lex Soft
  • 2,308
  • 2
  • 13
  • 13
0
  1. The right thing to do is to have the state in the parent component, to avoid ref and what not
  2. An issue is to avoid constantly updating all children when typing into a field
  3. Therefore, each child should be a Component (as in not a PureComponent) and implement shouldComponentUpdate(nextProps, nextState)
  4. This way, when typing into a form field, only that field updates

The code below uses @bound annotations from ES.Next babel-plugin-transform-decorators-legacy of BabelJS 6 and class-properties (the annotation sets this value on member functions similar to bind):

/*
© 2017-present Harald Rudell <harald.rudell@gmail.com> (http://www.haraldrudell.com)
All rights reserved.
*/
import React, {Component} from 'react'
import {bound} from 'class-bind'

const m = 'Form'

export default class Parent extends Component {
  state = {one: 'One', two: 'Two'}

  @bound submit(e) {
    e.preventDefault()
    const values = {...this.state}
    console.log(`${m}.submit:`, values)
  }

  @bound fieldUpdate({name, value}) {
    this.setState({[name]: value})
  }

  render() {
    console.log(`${m}.render`)
    const {state, fieldUpdate, submit} = this
    const p = {fieldUpdate}
    return (
      <form onSubmit={submit}> {/* loop removed for clarity */}
        <Child name='one' value={state.one} {...p} />
        <Child name='two' value={state.two} {...p} />
        <input type="submit" />
      </form>
    )
  }
}

class Child extends Component {
  value = this.props.value

  @bound update(e) {
    const {value} = e.target
    const {name, fieldUpdate} = this.props
    fieldUpdate({name, value})
  }

  shouldComponentUpdate(nextProps) {
    const {value} = nextProps
    const doRender = value !== this.value
    if (doRender) this.value = value
    return doRender
  }

  render() {
    console.log(`Child${this.props.name}.render`)
    const {value} = this.props
    const p = {value}
    return <input {...p} onChange={this.update} />
  }
}
Harald Rudell
  • 787
  • 6
  • 7
0

The concept of passing data from parent to child and vice versa is explained.

import React, { Component } from "react";
import ReactDOM from "react-dom";

// taken refrence from https://gist.github.com/sebkouba/a5ac75153ef8d8827b98

//example to show how to send value between parent and child

//  props is the data which is passed to the child component from the parent component

class Parent extends Component {
  constructor(props) {
    super(props);

    this.state = {
      fieldVal: ""
    };
  }

  onUpdateParent = val => {
    this.setState({
      fieldVal: val
    });
  };

  render() {
    return (
      // To achieve the child-parent communication, we can send a function
      // as a Prop to the child component. This function should do whatever
      // it needs to in the component e.g change the state of some property.
      //we are passing the function onUpdateParent to the child
      <div>
        <h2>Parent</h2>
        Value in Parent Component State: {this.state.fieldVal}
        <br />
        <Child onUpdate={this.onUpdateParent} />
        <br />
        <OtherChild passedVal={this.state.fieldVal} />
      </div>
    );
  }
}

class Child extends Component {
  constructor(props) {
    super(props);

    this.state = {
      fieldValChild: ""
    };
  }

  updateValues = e => {
    console.log(e.target.value);
    this.props.onUpdate(e.target.value);
    // onUpdateParent would be passed here and would result
    // into onUpdateParent(e.target.value) as it will replace this.props.onUpdate
    //with itself.
    this.setState({ fieldValChild: e.target.value });
  };

  render() {
    return (
      <div>
        <h4>Child</h4>
        <input
          type="text"
          placeholder="type here"
          onChange={this.updateValues}
          value={this.state.fieldVal}
        />
      </div>
    );
  }
}

class OtherChild extends Component {
  render() {
    return (
      <div>
        <h4>OtherChild</h4>
        Value in OtherChild Props: {this.props.passedVal}
        <h5>
          the child can directly get the passed value from parent by this.props{" "}
        </h5>
      </div>
    );
  }
}

ReactDOM.render(<Parent />, document.getElementById("root"));
akshay
  • 141
  • 1
  • 13
  • 1
    I would suggest you to inform the previous answer owner as a comment to add the description provided by you in his answer. Followed by reffering your name for a courtesy. – Thomas Easo Aug 28 '18 at 14:20
  • @ThomasEaso I cant find him here, I checked it. Is there any other suggestions – akshay Aug 28 '18 at 14:39
  • click on the "edit" button to the left of his icon and modify his answer and submit. it will go to a moderator and they'll do the needful for your valuable comments. – Thomas Easo Aug 29 '18 at 02:03