0

I have a main component which stores the state and has a function called handleBtnClick which manipulates the state value.

I have another component which calls the function handleBtnClick when a button is pressed. I have a displayComponent that I pass the state value to display in the form.

I know setState is asynchronous, so the displayComponent value is always one step behind. How can I fix this using the callback in setState or is there another way?

MainComponent

class SomeComponent extends React.Component {
  constructor (props) {
    super (props)
    this.state = {value: 1}
  }

  handleBtnClick = (values) => {      
    const newValue = this.state.value * values;
    this.setState({value: newValue});
  }

  render() {
    return (
      <div>
        <ButtonComponent onClick={this.handleBtnClick} />              
        <DisplayComponent displayValue={this.state.value}/>
      </div>
    );
  }
}

ButtonComponent

class ButtonComponent extends React.Component {
  render() {
    return (
      <div>
        <button onClick={() => this.props.onClick(123)}> // pretent this value always changes
                    Button
        </button>
      </div>
    );
  }
}

DisplayComponent

class DisplayComponent extends React.Component {
  render() {
    return (
      <div>
        {this.probs.displayValue}
      </div>
    );
  }
}

EDIT:

Sandbox example here (thanks Shubham): https://codesandbox.io/s/5kx9yk7934

On the first button click the value displayed in label is 0, even though it should be 4. On second click it updates to 4.

How can I make sure on first click the value is always correct (i.e 4) by only using the state value for calculation.

TylarBen
  • 801
  • 3
  • 8
  • 11
  • Can you explain what you mean by "one step behind"? In the code you posted, the button handler calls the function from the parent, which calls setState(), which causes DisplayComponent to re-render with the new value. – ouni Jun 08 '18 at 15:52
  • If works as expected only if you correct the type in `this.probs.displayValue` to `this.props.displayValue` check this sandbox https://codesandbox.io/s/00j699v4xl – Shubham Khatri Jun 08 '18 at 16:54
  • @ouni If you load this: https://codesandbox.io/s/5kx9yk7934 (thanks Shubham), on first button click, the total value is still 0, even though it should be 4. On the second click it updates to 4. – TylarBen Jun 08 '18 at 19:13
  • @ShubhamKhatri Updated example here: https://codesandbox.io/s/5kx9yk7934 – TylarBen Jun 08 '18 at 19:14
  • @TylarBen, setState calls are batched and also setState is asynchronous, so updating state doesn't reflect the change immediately. I updated the sandbox to work properly, Check https://codesandbox.io/s/xlwp6zv054. Also check this https://stackoverflow.com/questions/41278385/setstate-doesnt-update-the-state-immediately/41278440#41278440 – Shubham Khatri Jun 09 '18 at 04:26

2 Answers2

0

setState will update your state and, on state change, your main component will rerender with new state and so yourDisplayComponent to.

Your DislayComponent has a typo: this.probs

Both your button and display could be stateless components:

const DisplayComponent = ({displayValue}) => <div>{displayValue}</div>

Since onClick is used by react as a click listener, prefer passing handleClick props than onClick.

const ButtonComponent = ({handleClick}) => <div><button onClick={() => handleClick(123)}> </button></div>
0

In your sandbox, the SomeComponent component was not handling state correctly: there were multiple calls to setState, which is asynchronous, and also there were three values being stored (value1, value2, total), when it only requires one (a stack of operands to add), and you can derive the total from that on every render:

import React from "react";
import { render } from "react-dom";
class ButtonComponent extends React.Component {
  render() {
    return (
      <div>
        <button onClick={() => this.props.onClick(2)}>
          {" "}
          // pretent this value always changes Button
        </button>
      </div>
    );
  }
}
class DisplayComponent extends React.Component {
  render() {
    return <div>{this.props.displayValue}</div>;
  }
}

class SomeComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { stack: [] };
    this.get_total = this.get_total.bind(this);
  }

  handleBtnClick = value => {
    this.setState({ stack: this.state.stack.concat(value) });
  };

  get_total() {
    return this.state.stack.reduce((result, value) => {
      return result + value;
    }, 0);
  }

  render() {
    return (
      <div>
        <ButtonComponent onClick={this.handleBtnClick} />
        <DisplayComponent displayValue={this.get_total()} />
      </div>
    );
  }
}

render(<SomeComponent />, document.getElementById("root"));
ouni
  • 3,233
  • 3
  • 15
  • 21
  • Did this help you? In React, we try to store the minimum amount of state that we can, and then compute values on-the-fly, when we need them. – ouni Jun 19 '18 at 14:18