Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
157 views
in Technique[技术] by (71.8m points)

javascript - How to pass react props/state of parent by reference, not value?

I am using react hooks and attempting to pass the state of a parent to multiple components. The problem is when the components which I want to pass the state to are themselves part of a state. I've created an example which illustrates my problem, I have a button which increments a counter and appends another button to a list. I'd like to see the list of buttons displayed and all reading the same counter value from the state, but when I append a new button, the value of the counter is always passed by value at the time of creating the button, and it does not update. In my project (not shown here) I've circumvented this problem by passing functions to the components to retrieve the parent state and manually update all the components, however by doing this I feel that I am really breaking the design principles of react with unnecessarily complex code which leads to a lot more problems.

my example with buttons:

import React from 'react'

function Button(props){
  return <button onClick={()=>{
    // increment the counter
    props.setcounter(cur=>cur+1);

    // add another button
    // these props are not passed by reference: how do I pass these by reference?
    props.setitems(items=>items.concat(<tr><td><Button {...props}/></td></tr>))
    }}>
    {props.counter}
    </button>
}
function App() {
  const [items,setitems]=React.useState([]);
  const [counter,setcounter]=React.useState(0);
  return (
    <div className="App">
      <table>
    <th><td>Button with props passed by reference:</td></th>
    <tr><td><Button counter={counter} setcounter={setcounter} setitems={setitems} /></td></tr>
      </table>
      <table>
       <th><td>Button with props passed by value:</td></th>
    {items}
      </table>
    </div>
  );
}

export default App;

function Button(props){
  return <button onClick={()=>{
    // increment the counter
    props.setcounter(cur=>cur+1);

    // add another button
    // these props are not passed by reference: how do I pass these by reference?
    props.setitems(items=>items.concat(<tr><td><Button {...props}/></td></tr>))
    }}>
    {props.counter}
    </button>
}
function App() {
  const [items,setitems]=React.useState([]);
  const [counter,setcounter]=React.useState(0);
  return (
    <div className="App">
      <table>
    <th><td>Button with props passed by reference:</td></th>
    <tr><td><Button counter={counter} setcounter={setcounter} setitems={setitems} /></td></tr>
      </table>
      <table>
       <th><td>Button with props passed by value:</td></th>
    {items}
      </table>
    </div>
  );
}


ReactDOM.render(
  <App />,
  document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>

<div id="root"></div>
question from:https://stackoverflow.com/questions/65890666/how-to-pass-react-props-state-of-parent-by-reference-not-value

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The problem is when the components which I want to pass the state to are themselves part of a state.

This is indeed the problem, and an anti-pattern in React which as you've discovered creates stale values. Ideally you should not be keeping any components in state, and if you do, they must be completely pure (i.e. not liable to recieve new props during their life cycle).

What's actually happening when you call this state setter:

props.setitems(items=>items.concat(<tr><td><Button {...props}/></td></tr>))

Is this, assuming it's the first time its being called:

<Button counter={0} ... />

Not only will that button only ever have the value 0 as its counter, but any buttons it creates in turn will have the same value. What you're talking about doesn't actually make sense in a React context because the whole point is to break referentially (and strict equality more generally) in order to trigger rerenders and effects.

We want to keep our state immutable which means recreating any parts of it which change on every render. For what you're doing to work, you would have to recreate the entire store of buttons by looping as many times as the length of items whenever you create a new one.

The way you should be thinking about this is how to derive the UI from data, not store it as data:

function Button(props){
  return <button onClick={()=>{
      props.setcounter(cur=>cur+1);
      props.setitems(items=> [...items, 1])
    }}>
    {props.counter}
    </button>
}

function App() {
  const [items,setitems]=React.useState([1]);
  const [counter,setcounter]=React.useState(0);
  return (
    <div className="App">
       {items.map(() => <Button counter={counter} setcounter={setcounter} setitems={setitems}/>)}
    </div>
  );
}


ReactDOM.render(
  <App />,
  document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>

<div id="root"></div>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...