facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
228.69k stars 46.81k forks source link

useReducer - Unable to retrieve updated values #15961

Closed six-666 closed 5 years ago

six-666 commented 5 years ago
import React, { useEffect, useReducer } from 'react';

const initialState = {
  count: 0
};

function reducer(state: any, action: any) {
  switch (action.type) {
    case 'setCount':
      return {count: 999};
    default:
      return state;
  }
}

const About = function () {
  const [state, dispatch] = useReducer(reducer, initialState);

  function init() {
    dispatch({ type: 'setCount' })
    getData();
  }

  function getData() {
    // Why count is 0,   The result I expect is 999
    http('/', { count: state.count });
  }

  useEffect(() => {
    init();
  }, []);

  return null
};

export default About;
dmytro-lymarenko commented 5 years ago

The same issue as this #15933. From official docs https://reactjs.org/docs/hooks-reference.html#useeffect:

Note

If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders.

So, to get updated count you just need to use useEffect:

useEffect(() => {
  // This code will be run every time count is changed
  console.log(count); // or any other manipulation like HTTP request
}, [count]);

Also, read this https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies to avoid such issues.

six-666 commented 5 years ago

@dmytro-lymarenko useReducer Using the above method can cause a dead cycle.

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

const initialState = {
  count: 0
};

function reducer(state: any, action: any) {
  switch (action.type) {
    case 'setCount':
      return {count: 999};
    default:
      return state;
  }
}

const About = function () {
  const [state, dispatch] = useReducer(reducer, initialState);

  function init() {
    dispatch({ type: 'setCount' })
    getData();
  }

  function getData() {
    // Why count is 0,   The result I expect is 999
    http('/', { count: state.count });
  }

  useEffect(() => {
    init();
  }, [state]);

  return null
};

export default About;
dmytro-lymarenko commented 5 years ago

Count is still 0 because you didn't provide correct dependencies for useEffect

dmytro-lymarenko commented 5 years ago

Here is the working example: https://codesandbox.io/s/interesting-tereshkova-qlwv7

otakustay commented 5 years ago

Besides the dependency issue, useReducer will not update value immediately so you should call getData in another effect:

const About = function () {
  const [state, dispatch] = useReducer(reducer, initialState);

  function init() {
    dispatch({ type: 'setCount' })
  }

  useEffect(() => {
    init();
  }, []);

  useEffect(() => {
    if (state.count === 0) {
      return;
    }

    http('/', { count: state.count });
  }, [state]);

  return null
};
six-666 commented 5 years ago

@otakustay GetData function needs to be external, how to deal with.

otakustay commented 5 years ago

Make getData receive count as argument.

function getData(count) {
    http('/', {count});
}

const About = function () {
    const [state, dispatch] = useReducer(reducer, initialState);

    function init() {
      dispatch({ type: 'setCount' })
    }

    useEffect(() => {
      init();
    }, []);

    useEffect(() => {
      if (state.count === 0) {
        return;
      }

      getData(state.count);
    }, [state]);

    return null
  };