AnswerBun.com

How to synchronously change state within useState

Stack Overflow Asked by Michael Gee on January 3, 2022

Hey guys I am new to using React Hooks but couldn’t find it on google directly. I am attempting to nest setState callback so that the state can update synchronously but haven’t had success as I get undefined values. The values within the state are reliant on other values within my state so I would ideally like it to run synchronously. I tried nesting it below, which works when it is a class component and use this.setState and use the callback, but doesn’t work when I attempt to use react hooks within a functional class.

Here is my code:

const [state, setState] = useState({numCols: 0, numRows: 0, cardWidth: 0, cardHeight: 0, containerWidth: 0});
  
  const {
    sheetList,
    sheetsTotalCount,
    sheetsMetadata,
    id,
    projectId,
    onEditButtonClick,
    handleInfiniteLoad,
    sheetsAreLoading,
    classes,
    permissions,
    onItemClick,
  } = props;

  const setCardSize = ({ width }) {
    setState({
      containerWidth: width > 0 ? width : defaultWidth
    },() => { 
      setState({numCols: Math.max(Math.floor(state.containerWidth / baseThumbnailWidth), 1) }, () => {
        setState({numRows: Math.ceil(sheetList.size / state.numCols)}, () => {
          setState({cardWidth: Math.floor(state.containerWidth / state.numCols - 2 * marginBetweenCards)}, () => {
            setState({cardHeight: Math.round(state.cardWidth * thumbnailProportion)});
          });
        });
      });
    });
  }

Ideally I would like the containerWidth variable to update first, then the numCols variable, then the cardWidth, then the cardHeight. Is there any way to do this synchronously so I don’t get an undefined value?

2 Answers

Seeing as you're calculating a load of variables that are dependant upon another, and you want the state to all update at the same time, why not split the calculations out and set state once at the end? Much more readable, and only need one setState call.

 const setCardSize = ({ width }) => {
    const containerWidth = width > 0 ? width : defaultWidth;
    const numCols = Math.max(Math.floor(containerWidth / baseThumbnailWidth), 1,);
    const numRows = Math.ceil(sheetList.size / numCols);
    const cardWidth = Math.floor(containerWidth / numCols - 2 * marginBetweenCards);
    const cardHeight = Math.round(cardWidth * thumbnailProportion);
    setState({ containerWidth, numCols, numRows, cardWidth, cardHeight });
  };

To answer the actual question though, if you want to cause the state update of one variable to immediately (or "synchronously" as you put it) update another state variable, then use useEffect.

You just give useEffect two parameters: a function to run every time a dependant variable changes, and then an array of those variables to keep an eye on.

It is cleaner (and faster, less bug-prone, and generally recommended for functional components) for each state variable to have its own useState, rather than just one large object, which I have also done here.

  const [containerWidth, setContainerWidth] = useState(0);
  const [numCols, setNumCols] = useState(0);
  const [numRows, setNumRows] = useState(0);
  const [cardWidth, setCardWidth] = useState(0);
  const [cardHeight, setCardHeight] = useState(0);

  const setCardSize = ({ width }) => setContainerWidth(width > 0 ? width : defaultWidth)
  useEffect(() => setNumCols(Math.max(Math.floor(containerWidth / baseThumbnailWidth), 1)) , [containerWidth])
  useEffect(() => setNumRows(Math.ceil(sheetList.size / numCols)), [numCols])
  useEffect(() => setCardWidth(Math.floor(containerWidth / numCols - 2 * marginBetweenCards)), [containerWidth])
  useEffect(() => setCardHeight(Math.round(cardWidth * thumbnailProportion)), [cardWidth])

Answered by Luke Storry on January 3, 2022

I'm sort of confused on what you want to achieve. But don't forget, unlike a class you have to set all properties a in state each time.

setState({numCols: Math.max(Math.floor(state.containerWidth / baseThumbnailWidth), 1) }

This code will replace all your state with just numCols. You want the rest of state in there like this, now only numCols will change, everything else will be the same.

setState({...state, numCols: Math.max(Math.floor(state.containerWidth / baseThumbnailWidth), 1) }

Next thing to remember is if you want to change state multiple time in one render use this form:

setState(oldState => {...oldState, newValue: 'newValue'});

This will allow for multiple updates to state in one render with the last value set instead of on the last render. For example:

const [state, setState] = useState(0); // -> closed on this state's value!

setState(state + 1);
setState(state + 1);
setState(state + 1); //State is still 1 on next render 
// because it is using the state which happened on the last render.
// When this function was made it "closed" around the value 0
// (or the last render's number) hence the term closure.

vs this:

const [state, setState] = useState(0);

setState(state => state + 1);
setState(state => state + 1);
setState(state => state + 1); //State is 3 on next render.

But why not just calculate the values synchronously?

  const setCardSize = (width) => {
    const containerWidth = width > 0 ? width : defaultWidth;
    const numCols = Math.max(Math.floor(containerWidth / baseThumbnailWidth), 1);
    const numRows = Math.ceil(sheetList.size / numCols);
    const cardWidth = Math.floor(containerWidth / numCols - 2 * marginBetweenCards);
    const cardHeight = Math.round(cardWidth * thumbnailProportion);
    setState({containerWidth, numCols, numRows, cardWidth, cardHeight});
  }

Check out the docs it discusses

Unlike the setState method found in class components, useState does not automatically merge update objects.

If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value. Here’s an example of a counter component that uses both forms of setState:

Answered by Diesel on January 3, 2022

Add your own answers!

Related Questions

Unable to ATTACH a second DB with SQLite Database and JDBC

1  Asked on December 7, 2020 by tacitus86

     

Create 3×3 responsive layout with CSS grid

1  Asked on December 7, 2020 by smithy

   

SQL query based on three tables with restriction

2  Asked on December 7, 2020 by derrick

 

javascript get Pacific Timezone

1  Asked on December 7, 2020

   

Why define a struct with single private field of unit type?

1  Asked on December 6, 2020 by m-bat

 

Read more Laravel

1  Asked on December 6, 2020 by user11916445

   

npx: the shell-auto-fallback argument has been removed

2  Asked on December 6, 2020 by mike-earley

         

Cross-Process Memory Filesystem In Python

0  Asked on December 6, 2020 by eliran-abdoo

         

HTML Template not rendering on screen

1  Asked on December 6, 2020 by ojm

     

WOE in a magrittr Pipe

2  Asked on December 6, 2020 by gojomoso

 

Convert object of arrays to array of objects

1  Asked on December 5, 2020 by drmrbrewer

       

Ask a Question

Get help from others!

© 2023 AnswerBun.com. All rights reserved. Sites we Love: PCI Database, MenuIva, UKBizDB, Menu Kuliner, Sharing RPP, SolveDir