Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React state with calculated fields

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 be Calculated based on current State and Props:

enter image description here

The question: what is the best way to update calculated fields of the state (based on other fields of state and props)?

Ugly way to do it:

componentDidUpdate(){
    this.setState({calculatedField:calculate(this.props,this.state)})) 
}

In this case I get infinite loop of updates or in the best case (if I use PureComponent) double rendering invocation.

The best solution I found so far (but still ugly): Is to create a calculated object in state, which contains calculated fields and updated in componentWillUpdate avoiding setState:

componentWillUpdate(nextProps,nextState){
   nextState.calculated.field1=f(nextProps,nextState)
}

class ParentComponent extends React.Component {
  constructor(props, ctx) {
    super(props,ctx)
    this.state={A:"2"}
  }

  render() {
    console.log("rendering ParentComponent")
    return <div>
      <label>A=<input value={this.state.A} onChange={e=>{this.setState({A:e.target.value})}} /></label> (stored in state of Parent component)
      <ChildComponent A={this.state.A} />
    </div>
  }
}

class ChildComponent extends React.PureComponent {
  constructor(props,ctx) {
    super(props,ctx);
    this.state={
      B:"3",
      Calculated:{}
    }
  }

  render() {
    console.log("rendering ChildComponent")
    return <div>
      <label>B=<input value={this.state.B} onChange={e=>{this.setState({B:e.target.value})}} /></label> (stored in state of Child component state)
      <div>
        f(A,B)=<b>{this.state.Calculated.result||""}</b>(stored in state of Child component)
        <button onClick={e=>{ this.setState({Calculated:{result:new Date().toTimeString()}}) }}>Set manual value</button>
      </div>
    </div>
  }

  componentWillUpdate(nextProps, nextState) {
    this.state.Calculated.result = getCalculatedResult(nextProps.A, nextState.B)
  }

  componentWillReceiveProps(nextProps) {
    this.state.Calculated.result = getCalculatedResult(nextProps.A, this.state.B)
  }

  componentWillMount() {
    this.state.Calculated.result = getCalculatedResult(this.props.A, this.state.B)
  }
}

function getCalculatedResult(a,b) {
  const aNum = Number(a)||0
  const bNum = Number(b)||0;
  const result = (aNum*bNum).toString();
  return result;
}

ReactDOM.render(<ParentComponent/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>

This is also ugly solution and React does not recommended to mutate state avoiding setState. So what is right solution for that?

NOTE:

In my real application I cannot recalculate f(a,b) every single time during rendering, because it's actually complex object, so I need to cache it somehow and the best way is in the state.

like image 281
Philipp Munin Avatar asked Feb 22 '18 15:02

Philipp Munin


People also ask

What is are the correct method's for updating state having calculations?

State can be updated in response to event handlers, server responses, or prop changes. This is done using the setState() method.

How do you make a global state in React?

Role of the global state. In React, originally, the state is held and modified within the same React component . In most applications, different components may need to access and update the same state. This is achieved by introducing the global states in your app.

How do you pass Dostate as props in React?

First, click on App and observe its state under the Hooks section on the right pane. Second, click on a given player component and examine its props. Finally, click on any of the items in the page and see how the state and props of the parent and child components are updated, respectively.

How do I share a state between components in React?

In React, sharing state is accomplished by moving it up to the closest common ancestor of the components that need it. This is called “lifting state up”. We will remove the local state from the TemperatureInput and move it into the Calculator instead.


1 Answers

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

like image 90
elquimista Avatar answered Oct 22 '22 08:10

elquimista