What's the right way to pass form element state to sibling/parent elements?

后端 未结 10 713
情话喂你
情话喂你 2020-11-29 15:04
  • 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.
  • M
相关标签:
10条回答
  • 2020-11-29 15:12

    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.

    0 讨论(0)
  • 2020-11-29 15:14

    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.

    0 讨论(0)
  • 2020-11-29 15:18

    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

    • Thinking in React
    • Flux

    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.

    0 讨论(0)
  • 2020-11-29 15:20

    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)
    
    0 讨论(0)
  • 2020-11-29 15:20

    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.

    0 讨论(0)
  • 2020-11-29 15:25

    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 .

    0 讨论(0)
提交回复
热议问题