React state with calculated fields

后端 未结 6 1846
半阙折子戏
半阙折子戏 2021-02-13 18:24

I have a react component, which has properties and state. Some fields of state contain input data (uplifted from input control), but there is also fields in the state that must

相关标签:
6条回答
  • 2021-02-13 18:47

    You can save calculated result in this.calculated instead of this.state. It is dependent data. All data which causes update and render is already in state and props.

    class Component extends React.Component {
      constructor(props) {
        super(props)
        state = {
          b: 0
        }
      }
    
      updateThis = (event) => {
        this.setState({ b: event.target.value });
      }
    
      componentWillUpdate(nextProps,nextState){
        this.calculated.field1=f(nextProps.a, nextState.b)
      }
    
      render() {
        return (
          <form>
            A = <input onChange={this.props.updateParent} value={this.props.a} /> <br>
            B = <input onChange={this.updateThis} value={this.state.b} /> <br>
            f(A,B) = {this.calculated.field1} <br>
          </form>
        );
      }
    }
    
    class ParentComponent extends React.Component {
      constructor(props) {
        super(props)
        state = {
          a: 0
        }
      }
    
      render() {
         return (
           <Component
             updateParent={event=>this.setState({a: event.target.value})}
             a={this.state.a}
           />
         }
      }
    }
    
    0 讨论(0)
  • 2021-02-13 18:50

    Do not include redundant information in your state.

    A simplified example is having firstName and lastName in your state. If we want to display the full name in your render method, you would simply do:

    render() {
        return <span>{`${this.state.firstName} ${this.state.lastName}`}</span>
    
    }
    

    I like this example because it's easy to see that adding a fullName in our state, that just holds ${this.state.firstName} ${this.state.lastName} is unnecessary. We do string concatenation every time our component renders, and we're okay with that because it's a cheap operation.

    In your example, your calculation is cheap so you should do it in the render method as well.

    0 讨论(0)
  • 2021-02-13 18:51

    I wouldn't advice you to store your calculated value inside your state. My approach would be more like this:

    import PropTypes from 'prop-types';
    import React from 'react';
    
    class Component extends React.Component {
      static defaultProps = { value: 0 };
    
      static propTypes = { value: PropTypes.number };
    
      state = { a: 0, b: 0 };
    
      result = () => this.state.a + this.state.b + this.props.value;
    
      updateA = e => this.setState({ a: +e.target.value });
    
      updateB = e => this.setState({ b: +e.target.value });
    
      render() {
        return (
          <div>
            A: <input onChange={this.updateA} value={this.state.a} />
            B: <input onChange={this.updateB} value={this.state.b} />
            Result: {this.result()}
          </div>
        );
      }
    }
    

    The problem with storing the calculation inside your state is, that the calculation can be mutated by multiple sources. If you use my solution, there is no way, that anything can overwrite the calculation WITHOUT using the correct function to calculate them.

    0 讨论(0)
  • 2021-02-13 18:56

    You're first attempt is the right way to solve this problem. However, you need to add a check to see if state has actually changed:

    componentDidUpdate(prevProps, prevState){
        if(prevState.field !== this.state.field){
            this.setState({calculatedField:calculate(this.props,this.state)})) 
        }
    }
    
    shouldComponentUpdate(nextProps, nextState) {
        return this.state.calculatedField !== nextState.calculatedField
    }
    

    You need to check the pieces of state and props that you use in your calculate method and make sure they have changed before updating state again. This will prevent the infinite loop.

    0 讨论(0)
  • 2021-02-13 18:57

    If you are using React 16.8.0 and above, you can use React hooks API. I think it's useMemo() hook you might need. For example:

    import React, { useMemo } from 'react'
    
    const MyComponent = ({ ...props }) => {
      const calculatedValue = useMemo(
        () => {
          // Do expensive calculation and return.
        },
        [a, b]
      )
    
      return (
        <div>
          { calculatedValue }
        </div>
      )
    }
    

    For more details, refer to the React documentation

    0 讨论(0)
  • 2021-02-13 19:00

    It looks like the "state" is the place to store everything (even computed values) you'll need to use on the render function, but usually we have the problem you describe.

    Since React 16.3 a new approach for this situation has been given in the way of the static getDerivedStateFromProps (nextProps, prevState) "lifecycle hook".

    You should update at least to this version if you haven't, and follow the advice given by the React Team on their blog.

    Here is the official documentation for this functionality.

    The issue here is that this function is invoked before every render, and being "static" you cannot access the current instance previous props, which is usually needed to decide if the computed value must be generated again or not (I suppose this is your case, as you have stated your computation process is heavy). In this case, the React team suggests to store in the state the values of the related props, so they can be compared with the new ones:

    if (nextProps.propToCompute !== prevState.propToComputePrevValue) {
      return {
        computedValue: Compute(nextProp.propToCompute),
        propToComputePrevValue: nextProps.propToCompute
      };
    }
    return null;
    
    0 讨论(0)
提交回复
热议问题