797

I am trying to learn hooks and the useState method has made me confused. I am assigning an initial value to a state in the form of an array. The set method in useState is not working for me, both with and without the spread syntax.

I have made an API on another PC that I am calling and fetching the data which I want to set into the state.

Here is my code:

<div id="root"></div>

<script type="text/babel" defer>
// import React, { useState, useEffect } from "react";
// import ReactDOM from "react-dom";
const { useState, useEffect } = React; // web-browser variant

const StateSelector = () => {
  const initialValue = [
    {
      category: "",
      photo: "",
      description: "",
      id: 0,
      name: "",
      rating: 0
    }
  ];

  const [movies, setMovies] = useState(initialValue);

  useEffect(() => {
    (async function() {
      try {
        // const response = await fetch("http://192.168.1.164:5000/movies/display");
        // const json = await response.json();
        // const result = json.data.result;
        const result = [
          {
            category: "cat1",
            description: "desc1",
            id: "1546514491119",
            name: "randomname2",
            photo: null,
            rating: "3"
          },
          {
            category: "cat2",
            description: "desc1",
            id: "1546837819818",
            name: "randomname1",
            rating: "5"
          }
        ];
        console.log("result =", result);
        setMovies(result);
        console.log("movies =", movies);
      } catch (e) {
        console.error(e);
      }
    })();
  }, []);

  return <p>hello</p>;
};

const rootElement = document.getElementById("root");
ReactDOM.render(<StateSelector />, rootElement);
</script>

<script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>

Neither setMovies(result) nor setMovies(...result) works.

I expect the result variable to be pushed into the movies array.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Pranjal
  • 8,083
  • 3
  • 8
  • 13

18 Answers18

885

Much like .setState() in class components created by extending React.Component or React.PureComponent, the state update using the updater provided by useState hook is also asynchronous, and will not be reflected immediately.

Also, the main issue here is not just the asynchronous nature but the fact that state values are used by functions based on their current closures, and state updates will reflect in the next re-render by which the existing closures are not affected, but new ones are created. Now in the current state, the values within hooks are obtained by existing closures, and when a re-render happens, the closures are updated based on whether the function is recreated again or not.

Even if you add a setTimeout the function, though the timeout will run after some time by which the re-render would have happened, the setTimeout will still use the value from its previous closure and not the updated one.

setMovies(result);
console.log(movies) // movies here will not be updated

If you want to perform an action on state update, you need to use the useEffect hook, much like using componentDidUpdate in class components since the setter returned by useState doesn't have a callback pattern

useEffect(() => {
    // action on update of movies
}, [movies]);

As far as the syntax to update state is concerned, setMovies(result) will replace the previous movies value in the state with those available from the async request.

However, if you want to merge the response with the previously existing values, you must use the callback syntax of state updation along with the correct use of spread syntax like

setMovies(prevMovies => ([...prevMovies, ...result]));
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • 88
    Hi, what about calling useState inside a form submit handler ? I am working on validating a complex form, and I call inside submitHandler useState hooks and unfortunately changes are not immediate ! – da45 Apr 19 '19 at 16:42
  • 5
    `useEffect` might not be the best solution though, since it doesn't support asynchronous calls. So, if we would like to make some asynchronous validation on `movies` state change, we have no control over it. – RA. Aug 20 '19 at 03:10
  • 7
    please note that while the advice is very good, the explanation of the cause can be improved - nothing to do with the fact whether or not `the updater provided by useState hook` is asynchronous, unlike `this.state` that could have been mutated if `this.setState` was synchronous, the Closure around `const movies` would remain the same even if `useState` provided a synchronous function - see the example in my answer – Aprillion Nov 20 '19 at 19:26
  • 5
    `setMovies(prevMovies => ([...prevMovies, ...result]));` worked for me – Mihir Mar 31 '20 at 20:07
  • 21
    It is logging the wrong result because you are logging a [stale closure](https://dmitripavlutin.com/react-hooks-stale-closures/) not because the setter is asynchronous. If async was the problem then you could log after a timeout, but you could set a timeout for an hour and still log the wrong result because async isn't what is causing the problem. – HMR May 12 '20 at 07:02
  • @HMR I understand what you are saying. Maybe i didn't explain it properly in my post. I did write that the value will be updated only in the next render. I will update my psot – Shubham Khatri May 12 '20 at 07:07
  • @ShubhamKhatri hey, I use the spread way to merge prev with a new response but it does not work for me, here's a main [question](https://stackoverflow.com/questions/63720438/how-to-use-hooks-in-correct-way?noredirect=1#comment112700130_63720669), can you please check it? – Oliver D Sep 04 '20 at 09:17
  • @ShubhamKhatri What if it's not an object of values. just single value – Praveen M P Feb 16 '21 at 18:20
  • `const [timeSlotList, setTimeSlotList] = useState("");` This Working for me .... ` `setTimeSlotList(() => [...timeSlotTime]); ` – Vinoth Smart Feb 18 '21 at 13:30
  • @da45 I found cloning the previous state in the setState hook fixed this. Like the above example. – etoxin Mar 07 '21 at 23:44
  • So what do you do if you need a change to be immediate? Like for example, when I click the Submit button, I need to immediately show a spinner, rather than wait for setLoading and then check loading on render. – gene b. Jun 07 '21 at 17:47
  • @geneb. the change between states won't be slow. It in fact won't even be visible to the naked eye. – Shubham Khatri Jun 08 '21 at 10:44
  • There's an easy way to do this in a form submit handler (or any event handler) https://stackoverflow.com/a/70405577/5823517 without `useEffect` – Tunn Dec 18 '21 at 17:35
  • @ShubhamKhatri What if I have to use the updated data soon after i setState in functional component. eg. fetch(url) .then(data => data.json()) .then(res => { setCityKey(res.Key); callXYZ(city) }) //<--here, i am getting null for 'city' FYI i've also tried using useEffect as told }) .catch(err => console.log('err', err)) – vins Jan 11 '22 at 21:46
  • 2
    Am I the only one frustrated with these frameworks trying to "simplify" life by adding so much more complex, non-intuitive workarounds? I learnt so much JavaScript, and now if I am creating a Set using useState, I need to somehow de-structure the Set, find the item to delete, and return a new Set with that array. What was the point of having Set then? And now updating a value using useState is not synchronous. I am spending most of my time writing workarounds for these "simple" frameworks. – Mahesh Oct 20 '22 at 22:32
  • This only worked for me after I added a condiitonal like so, {movies.length > 0 ? : "No Movie(s) found"}. The state was just not triggering a render otherwise – tribal Mar 02 '23 at 10:20
  • 1
    Very good explanation – Kushal Desai Jun 29 '23 at 04:28
  • This is helpful,not work like java. – pan he Jul 26 '23 at 07:28
522

Additional details to the previous answer:

While React's setState is asynchronous (both classes and hooks), and it's tempting to use that fact to explain the observed behavior, it is not the reason why it happens.

TLDR: The reason is a closure scope around an immutable const value.


Solutions:

  • read the value in render function (not inside nested functions):

      useEffect(() => { setMovies(result) }, [])
      console.log(movies)
    
  • add the variable into dependencies (and use the react-hooks/exhaustive-deps eslint rule):

      useEffect(() => { setMovies(result) }, [])
      useEffect(() => { console.log(movies) }, [movies])
    
  • use a temporary variable:

      useEffect(() => {
        const newMovies = result
        console.log(newMovies)
        setMovies(newMovies)
      }, [])
    
  • use a mutable reference (if we don't need a state and only want to remember the value - updating a ref doesn't trigger re-render):

      const moviesRef = useRef(initialValue)
      useEffect(() => {
        moviesRef.current = result
        console.log(moviesRef.current)
      }, [])
    

Explanation why it happens:

If async was the only reason, it would be possible to await setState().

However, both props and state are assumed to be unchanging during 1 render.

Treat this.state as if it were immutable.

With hooks, this assumption is enhanced by using constant values with the const keyword:

const [state, setState] = useState('initial')

The value might be different between 2 renders, but remains a constant inside the render itself and inside any closures (functions that live longer even after render is finished, e.g. useEffect, event handlers, inside any Promise or setTimeout).

Consider following fake, but synchronous, React-like implementation:

// sync implementation:

let internalState
let renderAgain

const setState = (updateFn) => {
  internalState = updateFn(internalState)
  renderAgain()
}

const useState = (defaultState) => {
  if (!internalState) {
    internalState = defaultState
  }
  return [internalState, setState]
}

const render = (component, node) => {
  const {html, handleClick} = component()
  node.innerHTML = html
  renderAgain = () => render(component, node)
  return handleClick
}

// test:

const MyComponent = () => {
  const [x, setX] = useState(1)
  console.log('in render:', x) // ✅
  
  const handleClick = () => {
    setX(current => current + 1)
    console.log('in handler/effect/Promise/setTimeout:', x) // ❌ NOT updated
  }
  
  return {
    html: `<button>${x}</button>`,
    handleClick
  }
}

const triggerClick = render(MyComponent, document.getElementById('root'))
triggerClick()
triggerClick()
triggerClick()
<div id="root"></div>
Aprillion
  • 21,510
  • 5
  • 55
  • 89
  • @AlJoslin at a first glance, that seems like a separate problem, even if it might be caused by closure scope. If you have a concrete question, please create a new stackoverflow question with code example and all... – Aprillion Jun 16 '20 at 07:59
  • 2
    actually I just finished a rewrite with useReducer, following @kentcdobs article (ref below) which really gave me a solid result that suffers not one bit from these closure problems. (ref: https://kentcdodds.com/blog/how-to-use-react-context-effectively) – Al Joslin Jun 16 '20 at 16:52
  • for some reason solution 2 is not working.. I get the callback, but the value is still empty. `useEffect(() => { console.log(movies) }, [movies])` this prints nothing .. – ACV Oct 22 '20 at 13:57
  • 1
    @ACV Solution 2 works fine for the original question. If you need to solve a different problem, YMMW, but I am still 100% sure that the quoted code works as documented and the problem is somewhere else. – Aprillion Oct 22 '20 at 20:25
  • All of these solutions require the use of useEffect. My problem is that my "movies" equivalent is an object I get from a Context provider and can be changed by many other components. I don't want to run the effect every time it's changed because my effect isn't setMovies - it's a different function I need to call only when a particular change is made to movies - a change I'm not seeing when needed because of a stale context. – Matt Frei Mar 15 '22 at 06:52
  • excelent answer – Gabriel Oliveira May 05 '22 at 22:26
  • @MattFrei useEffect was only used to illustrate that setting the state happens after a render and the state value was remembered from the previous render, it wasn't meant as an advice to actually call setState from useEffect... the rest of the explanation should apply even for useState in a Context provider, but feel free to point me to a new SO question if additional deep dive would be useful – Aprillion May 07 '22 at 07:29
  • Yes! That's what I see from source code, the `seState` is behavior LIKE async, but IS sync exactly. – fawinell Sep 23 '22 at 02:39
  • "_it wasn't meant as an advice to actually call `setState` from `useEffect`..._" I'm sorry, I'm missing how this answers the OP then. That does seem to be the solution from the "previous answer" and this one, though it also seems to be anti-pattern -- you're using a mechanism for achieving side effects to change state. That is/n't antipattern & why? – ruffin Dec 27 '22 at 18:15
  • both answers address the implied question from the OP: *Why do I see the previous value of state after I called `setState` from **wherever**?* Feel free to use whatever pattern as explained in https://beta.reactjs.org/ or any library that might be useful in your project, I am not telling you what you should/n't do, just why the OP didn't work. – Aprillion Dec 28 '22 at 19:22
  • FTR setting state from inside a Promise chain started from `useEffect` or from somewhere else in context / parent / library / callback / stream / action creator / saga / store / init / SSR magic / wherever else, they all work the same in regads to `setState` vs `the state value inside one render`. – Aprillion Dec 28 '22 at 19:32
27

I know that there are already very good answers. But I want to give another idea how to solve the same issue, and access the latest 'movie' state, using my module react-useStateRef it has 11,000+ weekly downloads.

As you understand by using React state you can render the page every time the state change. But by using React ref, you can always get the latest values.

So the module react-useStateRef let you use state's and ref's together. It's backward compatible with React.useState, so you can just replace the import statement

const { useEffect } = React
import { useState } from 'react-usestateref'

  const [movies, setMovies] = useState(initialValue);

  useEffect(() => {
    (async function() {
      try {

        const result = [
          {
            id: "1546514491119",
          },
        ];
        console.log("result =", result);
        setMovies(result);
        console.log("movies =", movies.current); // will give you the latest results
      } catch (e) {
        console.error(e);
      }
    })();
  }, []);

More information:

Aminadav Glickshtein
  • 23,232
  • 12
  • 77
  • 117
13

I just finished a rewrite with useReducer, following the Kent C. Dodds article (ref below) which really gave me a solid result that suffers not one bit from these closure problems.

See: https://kentcdodds.com/blog/how-to-use-react-context-effectively

I condensed his readable boilerplate to my preferred level of DRYness -- reading his sandbox implementation will show you how it actually works.

import React from 'react'

// ref: https://kentcdodds.com/blog/how-to-use-react-context-effectively

const ApplicationDispatch = React.createContext()
const ApplicationContext = React.createContext()

function stateReducer(state, action) {
  if (state.hasOwnProperty(action.type)) {
    return { ...state, [action.type]: state[action.type] = action.newValue };
  }
  throw new Error(`Unhandled action type: ${action.type}`);
}

const initialState = {
  keyCode: '',
  testCode: '',
  testMode: false,
  phoneNumber: '',
  resultCode: null,
  mobileInfo: '',
  configName: '',
  appConfig: {},
};

function DispatchProvider({ children }) {
  const [state, dispatch] = React.useReducer(stateReducer, initialState);
  return (
    <ApplicationDispatch.Provider value={dispatch}>
      <ApplicationContext.Provider value={state}>
        {children}
      </ApplicationContext.Provider>
    </ApplicationDispatch.Provider>
  )
}

function useDispatchable(stateName) {
  const context = React.useContext(ApplicationContext);
  const dispatch = React.useContext(ApplicationDispatch);
  return [context[stateName], newValue => dispatch({ type: stateName, newValue })];
}

function useKeyCode() { return useDispatchable('keyCode'); }
function useTestCode() { return useDispatchable('testCode'); }
function useTestMode() { return useDispatchable('testMode'); }
function usePhoneNumber() { return useDispatchable('phoneNumber'); }
function useResultCode() { return useDispatchable('resultCode'); }
function useMobileInfo() { return useDispatchable('mobileInfo'); }
function useConfigName() { return useDispatchable('configName'); }
function useAppConfig() { return useDispatchable('appConfig'); }

export {
  DispatchProvider,
  useKeyCode,
  useTestCode,
  useTestMode,
  usePhoneNumber,
  useResultCode,
  useMobileInfo,
  useConfigName,
  useAppConfig,
}

With a usage similar to this:

import { useHistory } from "react-router-dom";

// https://react-bootstrap.github.io/components/alerts
import { Container, Row } from 'react-bootstrap';

import { useAppConfig, useKeyCode, usePhoneNumber } from '../../ApplicationDispatchProvider';

import { ControlSet } from '../../components/control-set';
import { keypadClass } from '../../utils/style-utils';
import { MaskedEntry } from '../../components/masked-entry';
import { Messaging } from '../../components/messaging';
import { SimpleKeypad, HandleKeyPress, ALT_ID } from '../../components/simple-keypad';

export const AltIdPage = () => {
  const history = useHistory();
  const [keyCode, setKeyCode] = useKeyCode();
  const [phoneNumber, setPhoneNumber] = usePhoneNumber();
  const [appConfig, setAppConfig] = useAppConfig();

  const keyPressed = btn => {
    const maxLen = appConfig.phoneNumberEntry.entryLen;
    const newValue = HandleKeyPress(btn, phoneNumber).slice(0, maxLen);
    setPhoneNumber(newValue);
  }

  const doSubmit = () => {
    history.push('s');
  }

  const disableBtns = phoneNumber.length < appConfig.phoneNumberEntry.entryLen;

  return (
    <Container fluid className="text-center">
      <Row>
        <Messaging {...{ msgColors: appConfig.pageColors, msgLines: appConfig.entryMsgs.altIdMsgs }} />
      </Row>
      <Row>
        <MaskedEntry {...{ ...appConfig.phoneNumberEntry, entryColors: appConfig.pageColors, entryLine: phoneNumber }} />
      </Row>
      <Row>
        <SimpleKeypad {...{ keyboardName: ALT_ID, themeName: appConfig.keyTheme, keyPressed, styleClass: keypadClass }} />
      </Row>
      <Row>
        <ControlSet {...{ btnColors: appConfig.buttonColors, disabled: disableBtns, btns: [{ text: 'Submit', click: doSubmit }] }} />
      </Row>
    </Container>
  );
};

AltIdPage.propTypes = {};

Now everything persists smoothly everywhere across all my pages

jacefarm
  • 6,747
  • 6
  • 36
  • 46
Al Joslin
  • 765
  • 10
  • 14
13

React's useEffect has its own state/lifecycle. It's related to mutation of state, and it will not update the state until the effect is destroyed.

Just pass a single argument in parameters state or leave it a black array and it will work perfectly.

React.useEffect(() => {
    console.log("effect");
    (async () => {
        try {
            let result = await fetch("/query/countries");
            const res = await result.json();
            let result1 = await fetch("/query/projects");
            const res1 = await result1.json();
            let result11 = await fetch("/query/regions");
            const res11 = await result11.json();
            setData({
                countries: res,
                projects: res1,
                regions: res11
            });
        } catch {}
    })(data)
}, [setData])
# or use this
useEffect(() => {
    (async () => {
        try {
            await Promise.all([
                fetch("/query/countries").then((response) => response.json()),
                fetch("/query/projects").then((response) => response.json()),
                fetch("/query/regions").then((response) => response.json())
            ]).then(([country, project, region]) => {
                // console.log(country, project, region);
                setData({
                    countries: country,
                    projects: project,
                    regions: region
                });
            })
        } catch {
            console.log("data fetch error")
        }
    })()
}, [setData]);

Alternatively, you can try React.useRef() for instant change in the React hook.

const movies = React.useRef(null);
useEffect(() => {
movies.current='values';
console.log(movies.current)
}, [])
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • The last code example has no need for neither async nor await as you use the Promise API. That's only needed in the first – oligofren Sep 27 '21 at 07:57
11

The closure is not the only reason.

Based on the source code of useState (simplified below). Seems to me the value is never assigned right away.

What happens is that an update action is queued when you invoke setValue. And after the schedule kicks in and only when you get to the next render, these update action then is applied to that state.

Which means even we don't have closure issue, react version of useState is not going to give you the new value right away. The new value doesn't even exist until next render.

  function useState(initialState) {
    let hook;
    ...

    let baseState = hook.memoizedState;
    if (hook.queue.pending) {
      let firstUpdate = hook.queue.pending.next;

      do {
        const action = firstUpdate.action;
        baseState = action(baseState);            // setValue HERE
        firstUpdate = firstUpdate.next;
      } while (firstUpdate !== hook.queue.pending);

      hook.queue.pending = null;
    }
    hook.memoizedState = baseState;

    return [baseState, dispatchAction.bind(null, hook.queue)];
  }

function dispatchAction(queue, action) {
  const update = {
    action,
    next: null
  };
  if (queue.pending === null) {
    update.next = update;
  } else {
    update.next = queue.pending.next;
    queue.pending.next = update;
  }
  queue.pending = update;

  isMount = false;
  workInProgressHook = fiber.memoizedState;
  schedule();
}

There's also an article explaining the above in the similar way, https://dev.to/adamklein/we-don-t-know-how-react-state-hook-works-1lp8

windmaomao
  • 7,120
  • 2
  • 32
  • 36
11

I too was stuck with the same problem. As other answers above have clarified the error here, which is that useState is asynchronous and you are trying to use the value just after setState. It is not updating on the console.log() part because of the asynchronous nature of setState, it lets your further code to execute, while the value updating happens on the background. Thus you are getting the previous value. When the setState is completed on the background it will update the value and you will have access to that value on the next render.

If anyone is interested to understand this in detail. Here is a really good Conference talk on the topic.

https://www.youtube.com/watch?v=8aGhZQkoFbQ

Apostolos
  • 10,033
  • 5
  • 24
  • 39
Nirjal Mahat
  • 247
  • 3
  • 5
6

The setState function returned by the useState hook in React does not update the state immediately. Instead, it schedules the state update to be processed in the next render cycle. This is because React batches state updates for performance reasons.

If you are trying to access the updated state immediately after calling setState, you may not see the updated value right away. Instead, you can use the useEffect hook to perform actions after the state has been updated.

Here's an example to demonstrate how you can use useEffect to perform an action after the state update

 import React, { useState, useEffect } from 'react';

const MyComponent = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // This effect will run after each state update
    console.log('Count has been updated:', count);
  }, [count]);

  const incrementCount = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={incrementCount}>Increment</button>
    </div>
  );
};

In the above example, the useEffect hook is used to log the updated count value after each state update. By passing [count] as the dependency array to useEffect, the effect will only run when the count state changes.

Suresh B
  • 379
  • 4
  • 3
4

I found this to be good. Instead of defining state (approach 1) as, example,

const initialValue = 1;
const [state,setState] = useState(initialValue)

Try this approach (approach 2),

const [state = initialValue,setState] = useState()

This resolved the rerender issue without using useEffect since we are not concerned with its internal closure approach with this case.

P.S.: If you are concerned with using old state for any use case then useState with useEffect needs to be used since it will need to have that state, so approach 1 shall be used in this situation.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 2
    This answer is not useful. With regard to re-rendering and captured closure values this approach does not make the slightest difference. The only difference it ever makes it when the state value is deliberately set to `undefined`, in this case you will gain the `initialValue` again. Which is just a confusing way of doing it, because you could just set it to the initial value without extra steps. – Martin May 20 '22 at 08:53
  • Approach 2 pollutes the global space. Approach 1, as mentioned, is an antipattern at best. – ggorlen May 31 '22 at 20:12
4

Most of the answers here are about how to update a state based on its previous value, but I don't understand how that relates to the question

The useState set method is not reflecting a change immediately


React 18

useState is asynchronous:

When an event that triggers a certain code, occurs, the code starts running, and when it finshes, react will check if there was a state update and if it is the case, only then the value of the useState hook is updated and this leads to a new render in which the new value is availabe.

const [example,setExemple] = useState("")
//...
<button
  onClick={() => {
    const newValue = "new";
    setExample(newValue);
    console.log(example); // output "" and this is normal, because the component didn't rerenderd yet so the new value is not availabe yet
  }}
>
  Update state
</button>

Supposing we have a scenario where we have a state which depends on another state, for example we want to make an API call based on the new value of example every time it is updated and then store the data from response in another state anotherExample.
to achieve so we have two ways:

1. use the value of newValue:

<button
  onClick={async () => {
    const newValue = "new";
    const response = await axios.get(`http://127.0.0.1:5000/${newValue}`);
    setExample(newValue);
    setAnotherExample(response.data);
  }}
>
  test
</button>

since you know that example will receive this value, you can create your logic based on it directly.

2. trigger a useEffect to run each time example is updated by including example in its dependency array:

<button
  onClick={() => {
    const newValue = "new";
    setExample(newValue);
  }}
>
  test
</button>
useEffect(() => {
 async function test(){
  const response = await axios.get(`http://127.0.0.1:5000/${example}`);
  setAnotherExample(response.data);
 } 
 test();
}, [example])

so when example is updated with the event function the component rerenders, we are now in a new different render that once finished, useEffect will run because the value of example is different from what is was during the last render, and since it is a new different render, the new value of example useState hook is available here.

Note: the useEffect hook will run anyway during the first mount.

Which approach better?

  • while the first method will make all the work in one render (a better approach) "React groups multiple state updates into a single re-render for better performance" the second method will do it in two renders, the first when example is updated and the second when anotherExample is updated from inside useEffect

  • since the component only rerenders when the new value of a useState hook is different from the old one, so when newValue is equal to example the component will not rerender so the useEffect will not run and anotherExample will not be updated (a better approach), however in the first method the API is called anyway and we don't want to do that if there is no need also if this happens anotherExample will be updated (anotherExample will receive the same data it already contains because it is the same REQUEST since newValue is equal to example) but if the response in an object or an array then, Object.is method (that the useState hook utilizezs), cannot detect if the new value is equal to the previous one, therefore, the component will rerender

Conclusion:

As it is mentioned above, each one has its advantage, so it depends on the use case.

the second method is more recommanded, however the first can be more performant in some cases, for example when you are sure the code will only run when newValue gets a new value using onChange, or maybe when you want to use some other local variables that you will no longer have access to from inside useEffect

Ahmed Sbai
  • 10,695
  • 9
  • 19
  • 38
2

If we have to update state only, then a better way can be if we use the push method to do so.

Here is my code. I want to store URLs from Firebase in state.

const [imageUrl, setImageUrl] = useState([]);
const [reload, setReload] = useState(0);

useEffect(() => {
    if (reload === 4) {
        downloadUrl1();
    }
}, [reload]);


const downloadUrl = async () => {
    setImages([]);
    try {
        for (let i = 0; i < images.length; i++) {
            let url = await storage().ref(urls[i].path).getDownloadURL();
            imageUrl.push(url);
            setImageUrl([...imageUrl]);

            console.log(url, 'check', urls.length, 'length', imageUrl.length);
        }
    }
    catch (e) {
        console.log(e);
    }
};

const handleSubmit = async () => {
    setReload(4);
    await downloadUrl();
    console.log(imageUrl);
    console.log('post submitted');
};

This code works to put URLs in state as an array. This might also work for you.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 2
    `.push` mutates the current state, which is a [bad practice in React](https://stackoverflow.com/q/37755997/1218980). Here's the proper way to [update a state array](https://stackoverflow.com/q/26253351/1218980). – Emile Bergeron Mar 14 '22 at 22:43
  • Calling `setImageUrl` in a loop is another bad practice, which will trigger a new render for each time it was called since it's not batched when called asynchronously (outside React's lifecycle). The proper way would be to build the new array and then call `setImageUrl` only once. – Emile Bergeron Mar 14 '22 at 22:44
  • Also, using [`await` in a loop like that is inefficient](https://eslint.org/docs/rules/no-await-in-loop). Something like [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) would improve this. – Emile Bergeron Mar 14 '22 at 22:47
0

With custom hooks from my library, you can wait for the state values to update:

  1. useAsyncWatcher(...values):watcherFn(peekPrevValue: boolean)=>Promise - is a promise wrapper around useEffect that can wait for updates and return a new value and possibly a previous one if the optional peekPrevValue argument is set to true.

(Live Demo)

    import React, { useState, useEffect, useCallback } from "react";
    import { useAsyncWatcher } from "use-async-effect2";
    
    function TestComponent(props) {
      const [counter, setCounter] = useState(0);
      const [text, setText] = useState("");
    
      const textWatcher = useAsyncWatcher(text);
    
      useEffect(() => {
        setText(`Counter: ${counter}`);
      }, [counter]);
    
      const inc = useCallback(() => {
        (async () => {
          await new Promise((resolve) => setTimeout(resolve, 1000));
          setCounter((counter) => counter + 1);
          const updatedText = await textWatcher();
          console.log(updatedText);
        })();
      }, []);
    
      return (
        <div className="component">
          <div className="caption">useAsyncEffect demo</div>
          <div>{counter}</div>
          <button onClick={inc}>Inc counter</button>
        </div>
      );
    }
    
    export default TestComponent;
  1. useAsyncDeepState is a deep state implementation (similar to this.setState (patchObject)) whose setter can return a promise synchronized with the internal effect. If the setter is called with no arguments, it does not change the state values, but simply subscribes to state updates. In this case, you can get the state value from anywhere inside your component, since function closures are no longer a hindrance.

(Live Demo)

import React, { useCallback, useEffect } from "react";
import { useAsyncDeepState } from "use-async-effect2";

function TestComponent(props) {
  const [state, setState] = useAsyncDeepState({
    counter: 0,
    computedCounter: 0
  });

  useEffect(() => {
    setState(({ counter }) => ({
      computedCounter: counter * 2
    }));
  }, [state.counter]);

  const inc = useCallback(() => {
    (async () => {
      await new Promise((resolve) => setTimeout(resolve, 1000));
      await setState(({ counter }) => ({ counter: counter + 1 }));
      console.log("computedCounter=", state.computedCounter);
    })();
  });

  return (
    <div className="component">
      <div className="caption">useAsyncDeepState demo</div>
      <div>state.counter : {state.counter}</div>
      <div>state.computedCounter : {state.computedCounter}</div>
      <button onClick={() => inc()}>Inc counter</button>
    </div>
  );
}
Dmitriy Mozgovoy
  • 1,419
  • 2
  • 8
  • 7
0
var [state,setState]=useState(defaultValue)

useEffect(()=>{
   var updatedState
   setState(currentState=>{    // Do not change the state by get the updated state
      updateState=currentState
      return currentState
   })
   alert(updateState) // the current state.
})
Keith Hughitt
  • 4,860
  • 5
  • 49
  • 54
rjhcnf
  • 762
  • 6
  • 12
  • 1
    Don't do that. `setState`'s setter callback should be [pure](https://en.wikipedia.org/wiki/Pure_function). Also, here, `updatedState` would always be `undefined`. – Emile Bergeron Mar 14 '22 at 22:37
  • @EmileBergeron do you have a link to the documentation stating that the callbacks should be free of side effects? – Ricola Jun 08 '22 at 13:13
  • I dont have the link on hand, but it's documented alongside strict mode, which helps identify unwanted side-effects. – Emile Bergeron Jun 08 '22 at 20:04
0

Without any addtional NPM package

//...
const BackendPageListing = () => {
    
    const [ myData, setMyData] = useState( {
        id: 1,
        content: "abc"
    })

    const myFunction = ( x ) => {
        
        setPagenateInfo({
        ...myData,
        content: x
        })

        console.log(myData) // not reflecting change immediately

        let myDataNew = {...myData, content: x };
        
        console.log(myDataNew) // Reflecting change immediately

    }

    return (
        <>
            <button onClick={()=>{ myFunction("New Content")} }>Update MyData</button>
        </>
    )
GMKHussain
  • 3,342
  • 1
  • 21
  • 19
0

⚠️ Functional Components

I believe a super clean way would be to create a custom hook that provides the ability to pass a callback to the setter function, then it would be a 100% guarantee to do some actions exactly after the update of the state.

By taking a look at this post you can understand how to make the useStateCallback hook. Defining a state by using the useStateCallback would be like the following:

const [count, setCount] = useStateCallback(0);

const handleFooBar = () => {
  setCount(c => c + 1, () => { // The callback function
    // All actions here will be run exactly AFTER the update of the count state
  })
};
AmerllicA
  • 29,059
  • 15
  • 130
  • 154
-2

Not saying to do this, but it isn't hard to do what the OP asked without useEffect.

Use a promise to resolve the new state in the body of the setter function:

const getState = <T>(
  setState: React.Dispatch<React.SetStateAction<T>>
): Promise<T> => {
  return new Promise((resolve) => {
    setState((currentState: T) => {
      resolve(currentState);
      return currentState;
    });
  });
};

And this is how you use it (example shows the comparison between count and outOfSyncCount/syncCount in the UI rendering):

const App: React.FC = () => {
  const [count, setCount] = useState(0);
  const [outOfSyncCount, setOutOfSyncCount] = useState(0);
  const [syncCount, setSyncCount] = useState(0);

  const handleOnClick = async () => {
    setCount(count + 1);

    // Doesn't work
    setOutOfSyncCount(count);

    // Works
    const newCount = await getState(setCount);
    setSyncCount(newCount);
  };

  return (
    <>
      <h2>Count = {count}</h2>
      <h2>Synced count = {syncCount}</h2>
      <h2>Out of sync count = {outOfSyncCount}</h2>
      <button onClick={handleOnClick}>Increment</button>
    </>
  );
};
E_net4
  • 27,810
  • 13
  • 101
  • 139
Tunn
  • 1,506
  • 16
  • 25
  • 3
    `setState` setter callback should be [pure](https://en.wikipedia.org/wiki/Pure_function). – Emile Bergeron Mar 14 '22 at 22:18
  • 1
    I am in favor of emphasizing pure functions, but that link isn't specific to the `useState` callback (there are use cases for not using a pure function as the callback). Also, although this isn't "pure", it doesn't actually change any state either. I'm not saying this is the best way (I don't use it), just that it provides an alternative solution to the OPs question – Tunn Mar 15 '22 at 09:47
  • 2
    Using count and or callbacks hinders the design method behind usestate itself I'm afraid. – Jamie Nicholl-Shelley Mar 21 '22 at 20:13
  • 1
    Again, not saying this is the best way or to use it, just saying it works. Would be more useful to say what the issues would be in using it with a reproducible example as opposed to disregarding it from a design theory level – Tunn Mar 25 '22 at 14:30
  • 1
    "A design theory level" seems to be a perfectly valid reason to criticize something. Just because something works or is possible doesn't make it worth putting out there. Folks might actually use this pattern in spite of the warnings. There are much better ways to deal with the "problem" (it's not exactly a problem to begin with, just seems like that to folks who are unused to asynchronous code). – ggorlen May 31 '22 at 19:26
  • 1
    Not disagreeing with you. My point is that no one has given a concrete example or explanation on what the warnings or drawbacks are here, or a link that shows how this is incongruent with the design. I'm fine with the downvotes, but I don't find a comment like "there are much better ways" to be particularly insightful even though it may be perfectly valid, just show it or link to something :) – Tunn Jun 01 '22 at 15:14
-4
// replace
return <p>hello</p>;
// with
return <p>{JSON.stringify(movies)}</p>;

Now you should see, that your code actually does work. What does not work is the console.log(movies). This is because movies points to the old state. If you move your console.log(movies) outside of useEffect, right above the return, you will see the updated movies object.

  • 8
    Not sure why this answer is down voted heavily, it goes tell how to get "expected" console.log value by putting it outside useState function. Simple and sweet, if somebody wants to know why it is happening like that can refer to above detailed clarifications – vikramvi Mar 25 '21 at 05:16
  • Good try though – ADITYA AHLAWAT Sep 19 '22 at 08:32
-4

Use the Background Timer library. It solved my problem.

const timeoutId = BackgroundTimer.setTimeout(() => {
    // This will be executed once after 1 seconds
    // even when the application is the background
    console.log('tac');
}, 1000);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 1
    Adding a delay is not an actual solution.. its just a workaround.. even then, you dont need a library for that when you can just use a simple setTimeout – Prabhu Thomas Feb 23 '22 at 08:57