State returning wrong/late value

Asked

Viewed 416 times

2

The application: by clicking on a button, an event onClick is fired and a fetch There is an API that returns data in JSON is performed. Right after this data is transformed into an object, the application selects only the relevant values to be shown on screen and makes a console.log of this object.

The problem: As can be noticed in this GIF, the first click returns an empty object in the browser console. Later clicks return an object containing the values of the previous click.

Finally, follow the code:

const App = () => {
  const [dados, setDados] = React.useState({});

    async function handleClick(event) {
      const innerText = event.target.innerText;
      const API = `https://ranekapi.origamid.dev/json/api/produto/${innerText}`;
      const fetchProduct = await (await fetch(API)).json();
      const productInfo = {
        name: fetchProduct.nome,
        price: +fetchProduct.preco,
        img: fetchProduct.fotos[0].src,
    }

    setDados(dados => productInfo);
    console.log(dados);
  }

  return (
    <div>
      <button onClick={handleClick} style={{ marginRight: "1rem" }}>
        notebook
      </button>
      <button onClick={handleClick} style={{ marginRight: "1rem" }}>
        smartphone
      </button>
      <button onClick={handleClick}>tablet</button>
      <div>
        <h1>{dados.name}</h1>
        <p>{dados.price}</p>
        <img src={dados.img} alt={dados.name}/>
      </div>
    </div>
  );
};
  • @Rafaeltavares then, it turns out I need to use the method toLocaleString to format the product price to BRL. But as the object, at the first click, is empty, React returns an error stating that the value price within the commodity is Undefined and therefore fails to execute the toLocaleString. In short, I need you to setDados be updated BEFORE the information appears on the screen. But I’m not finding the correct logic.

  • To debug, I think it would be better to give: console.log(productInfo);

  • @Ricardosteps you need to update the value so that the dados is formatted? You can format the productInfo before putting in the setDados...

  • 1

    @Rafaeltavares his comments + the answer managed to give me a light. I managed to solve the problem. Thank you very much <3.

  • Try to use this way: &#xA;setDados(dados => {...dados, ...productInfo});&#xA;

1 answer

5


The setState is asynchronous

This is the expected behavior.

Calls to setState are asynchronous, React seeks to perform a batch update (batch) to cause less rendering. That answer talks a little about batch updates (batches).

In view of this behavior of setState, you will be able to accomplish a console.log state updated only in the next rendering, and not just below the setState.

Citing the documentation:

Why setState is giving me the wrong value?

Calls to setState are asynchronous - don’t trust this.state to reflect the new value immediately after calling setState. (...)

When setState is asynchronous?

Currently, setState is asynchronous within event handlers.

This ensures that, for example, both Parent how much Child flame setState after a click event, Child is not rendered twice. Instead, React performs all status updates at the end of the browser event. This results in a significant performance improvement for larger applications.

This is an implementation detail, so avoid depending on it directly. In future versions, React will do batch updates in more cases.

A practical example:

function Exemplo() {
  const [state, setState] = React.useState('primeiro render');

  React.useEffect(() => {
    setState('segundo render');
    console.log('useEffect após setState:', state);
  }, []);

  console.log('RENDER:', state);

  return <div>Estado: {state}</div>
}

ReactDOM.render(<Exemplo />, document.querySelector("#container"));
<div id="container"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>


The difference between an object and a function in the setState

A specific detail about your case setDados(dados => productInfo): there is a difference between using an object and a function in the setState. As your new state is independent of the current state, you can simply use setDados(productInfo).

As documented:

Passing an update function allows you to access the current value of state within it. Like the calls from setState are done in batches, this allows you to chain updates and ensure that they compose rather than conflict.

Below is an example with setTimeout 500ms to simulate a situation where this conflict happens. Click several times quickly on the same button to update the status then see the difference between the two modes:

function Exemplo() {
  const [stateUm, setStateUm] = React.useState(0);
  const [stateDois, setStateDois] = React.useState(0);

  function updateUm() {
    setTimeout(() => setStateUm(stateUm + 1), 500);
  }
  
  function updateDois() {
    setTimeout(() => setStateDois(val => val + 1), 500);
  }

  return (
    <div>
      <div>
        <p>stateUm: {stateUm}</p>
        <button onClick={updateUm}>setStateUm(stateUm + 1)</button>
      </div>
      <div>
        <p>stateDois: {stateDois}</p>
        <button onClick={updateDois}>setStateDois(val => val + 1)</button>
      </div>
    </div>
  );
}

ReactDOM.render(<Exemplo />, document.querySelector("#container"));
<div id="container"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

  • 1

    Apparently the problem was in setDados. I did what you reported there in the last paragraph and everything worked out.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.