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

Python, change a var by a string

4  Asked on February 21, 2021 by beardlongo

   

Vue – how to show an array of data in a table?

2  Asked on February 21, 2021 by jayk23

     

CMake unit testing – Unresolved external symbol

1  Asked on February 21, 2021 by chrispytoes

   

Break string into new rows after grepl

2  Asked on February 21, 2021 by ip2018

   

Send unicode sms with java

3  Asked on February 21, 2021 by hong4rc

     

Getting n1 n2 outputs after conversion

2  Asked on February 21, 2021 by icemilo

   

Is that possible to make for each user table in database

0  Asked on February 21, 2021 by haddadi-abdraouf

     

Run-time Error 1004; Error handling

2  Asked on February 20, 2021 by karl-drews

       

Password Generating program in python

2  Asked on February 20, 2021 by m-ismail

         

Error TS1086:An accessor cannot be declared in an ambient context

0  Asked on February 20, 2021 by leonardokunkel

 

Pass object data as FormData

2  Asked on February 20, 2021 by swarup-chavan

   

Iteration in Python throws results no desired

1  Asked on February 20, 2021 by catalina-hernndez

         

Ask a Question

Get help from others!

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