USESTATE SET方法不是立即反映更改

发布于 2025-02-11 02:05:32 字数 2119 浏览 2 评论 0 原文

我正在尝试学习钩子, usestate 方法使我感到困惑。我将以数组形式的状态分配一个初始值。 usestate 中的集合方法对我不适合有或没有差异语法。

我在另一台PC上制作了一个API,我正在调用并获取要设置在状态的数据。

这是我的代码:

<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>

setMovies(结果)也不是 setMovies(...结果)工作。

我希望将结果变量推入电影数组。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(21

梦醒灬来后我 2025-02-18 02:05:32

类似 .setState() in Class Components中/code>或 react.purecomponent ,使用 usestate 挂钩的更新程序的状态更新也是异步的,并且不会立即反射。

此外,这里的主要问题不仅是异步性质,而且是函数基于当前关闭而使用状态值的事实,状态更新将反映在下一个重新渲染中,而现有关闭不受影响,但是创建了新的。现在处于当前状态,挂钩中的值是通过现有关闭获得的,当重新渲染发生时,闭合会根据是否再次重新创建函数进行更新。

即使您添加了 settimeout 该功能,尽管超时会在一段时间后运行,但 settimeout 仍将从其 settimeout 仍将使用该值以前的关闭,而不是更新的关闭。

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

如果要在状态更新上执行操作,则需要使用 useffect 钩子,就像在类组件中使用 componentDidupdate ,因为 usestate 没有回调模式

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

/code>就需要更新状态的语法而言, 从异步请求获得。

但是,如果要将响应与先前现有的值合并,则必须使用状态更新的回调语法以及正确使用evrail语法

setMovies(prevMovies => ([...prevMovies, ...result]));

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]));
呆萌少年 2025-02-18 02:05:32

上一个答案::

时SetState 异步(包括类和钩子),很容易使用该事实来解释观察到的行为,不是 的原因 发生。

tldr:原因是封闭> const 值。


解决方案:

  • 读取渲染函数中的值(不在嵌套功能内):

      useffect(()=&gt; {setMovies(result)},[]),[])
      console.log(电影)
     
  • 将变量添加到依赖项中(并使用 react-hooks/详尽的deps eslint规则):

      useffect(()=&gt; {setMovies(result)},[]),[])
      useseffect(()=&gt; {console.log(movies)},[histove])
     
  • 使用临时变量:

      useseffect(()=&gt; {
        const newmovies =结果
        console.log(newmovies)
        SetMovies(Newmovies)
      },[])
     
  • 使用可变的参考(如果我们不需要状态并且只想记住值 - 更新ref不会触发重新渲染):

      const MoversRef = USEREF(initialValue)
      useeffect(()=&gt; {
        MOVIESREF.CURRENT =结果
        console.log(MistionRef.Current)
      },[])
     

说明为什么会发生:

如果async是唯一的原因,可以等待SetState()

但是,这两个 props 状态均为假定在1渲染期间不变

治疗 this.state 就像它是不变的。

使用挂钩,通过使用常数值 const 关键字:

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

该值在2个渲染之间可能有所不同,但是在2个渲染之间可能有所不同,但是在渲染本身和任何任何内部都保持常数闭合> 使用效果,事件处理程序,在任何承诺或settimeout中)。

考虑遵循伪造的,但同步,类似反应的实现:

// 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>

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>

む无字情书 2025-02-18 02:05:32

我知道已经有很好的答案了。但是我想给出另一个想法,如何解决同一问题,并使用我的模块 react-usestateref 每周下载11,000多个。

正如您通过使用React状态所理解的那样,您可以每次变化时都会渲染页面。但是,通过使用React Ref,您始终可以获取最新值。

因此,模块 react-usestateref 让您一起使用状态和ref。它向后与 react.usestate 兼容,因此您可以替换 import 语句

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);
      }
    })();
  }, []);

更多信息:

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:

不语却知心 2025-02-18 02:05:32

这里的大多数答案是关于如何根据其先前的值更新状态的,但我不明白这与问题有关

USESTATE SET方法没有立即反映更改


反应18

USESTATE是异步的:

当触发某个代码,发生某个代码,发生的事件发生时,代码开始运行,当它finshes finshes时,React会检查是否有状态更新,以及它是否存在 usestate 挂钩的值已更新,这导致了一个新渲染,其中新值可用。

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>

假设我们有一个方案如果我们的状态取决于另一个状态,例如,我们要根据示例的新值每次更新时进行API调用,然后将响应中的数据存储在另一个状态<代码> anotherexample 。
为了实现,我们有两种方法:

1。使用 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>

因为您知道示例将接收此值,因此可以直接基于它创建逻辑。

2。触发a useffect 每次运行 示例在其依赖项数组中:

<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])

因此,当示例使用事件函数更新时渲染一旦完成, useffect 将运行,因为示例的值与上次渲染和由于它是一种新的不同渲染,因此在此处可用示例 USESTATE HONK的新值。

注意: 使用效果挂钩在第一个安装座期间都会运行。

哪种方法更好?

  • 虽然第一种方法将使所有工作都在一个渲染中(一种更好的方法)“ react组多个状态更新到单个重新渲染中以获得更好的性能”,第二种方法将做到这一点在两个渲染中,第一个是示例更新时,第二个是 anotherexample 从内部更新 useffeft useeffect

  • 更新的,因为组件仅在新的时重新读取器 usestate 挂钩的值与旧的值不同,因此,当 newValue 等于示例>示例时,组件将不会启用,因此 usausefect 将不会运行, anotherexample 将不会更新

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 recommended, 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

青衫儰鉨ミ守葔 2025-02-18 02:05:32

正如上面的其他答案阐明了此处的错误,即 usestate 是异步的,您正在尝试在 setState 之后使用该值。它没有在 console.log()零件上更新,因为 setState 的异步性质,它允许您的进一步代码执行,而值更新发生在后台上。因此,您正在获得以前的值。当 setstate 在后台完成时,它将更新该值,您将可以在下一个渲染上访问该值。

如果有人有兴趣详细理解这一点。这是关于该主题的非常好的会议演讲。

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

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

桃气十足 2025-02-18 02:05:32

React的使用效应具有自己的状态/生命周期。它与状态突变有关,直到效果破坏之前,它不会更新状态。

只需将单个参数传递在使用效率参数数组中的参数或留空时,它将完美地工作。

const [data, setData] = useState();
useEffect(() => {
  setData({
    countries: ["Aus", "Bad"],
    projects: "projects",
    regions: "regions",
  });
}, []);

另外,您可以尝试react.useref()以立即更改React钩子。

const movies = React.useRef(null);
useEffect(() => {
  movies.current = "values";
  console.log(movies.current);
}, []);

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 in useEffect params array or leave it a blank, it will work perfectly.

const [data, setData] = useState();
useEffect(() => {
  setData({
    countries: ["Aus", "Bad"],
    projects: "projects",
    regions: "regions",
  });
}, []);

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);
}, []);
骷髅 2025-02-18 02:05:32

在肯特·C·多德斯(Kent C. Dodds)的文章(参考文献)之后,我刚刚完成了与用户介绍者的重写,这确实给了我可靠的结果,从这些封闭问题中却没有一点。

请参阅:

我将他可读的样板凝结为我首选的干燥水平 - 阅读他的沙盒实现将向您展示其实际工作方式。

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,
}

使用类似的用法:

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 = {};

现在,我所有页面上到处的一切都持续下去

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

北笙凉宸 2025-02-18 02:05:32

关闭不是唯一的原因。

基于 usestate 的源代码(下面简化)。在我看来,这个价值从未立即分配。

发生的事情是,当您调用 setValue 时,更新操作将排队。在时间表开始之后,只有当您进入下一个渲染时,这些更新操作才会应用于该状态。

这意味着即使我们没有关闭问题, usestate 的React版本也不会立即为您提供新的值。直到下一个渲染之前,新值甚至不存在。

  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();
}

还有一篇文章以类似的方式解释上述内容, https://dev.to/adamklein/we-don-t-know-how-now-now-now-now-state-hook-works-works-1lp8

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

万人眼中万个我 2025-02-18 02:05:32

我发现这很好。它没有定义状态(方法1)为例,

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

请尝试此方法(方法2),

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

而是在不使用使用效果的情况下解决了果兰德问题,因为我们不关心其内部封闭方法与这种情况。

PS:如果您关心在任何用例中使用旧状态,则需要使用使用效果的Usestate,因为它需要具有该状态,因此在这种情况下应使用方法1。

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.

女皇必胜 2025-02-18 02:05:32

如果我们只需要更新状态,那么如果我们使用推动方法来做到这一点,则可以使用更好的方法。

这是我的代码。我想从

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');
};

该代码可将URL作为数组置于状态。这也可能对您有用。

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.

春花秋月 2025-02-18 02:05:32

我想添加@aprillion和 @shubham-khatri建议的内容。

@shubham-khatri建议:

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

对吗?

但是我的代码就是这样( myFunction 在单击事件上触发)

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

此代码仍然无法正常工作。注意区别,区别是我有 async 。如果您拥有 async 代码将不等待更新 hives [movies] ,因此您必须这样做

const myfunction = useEffect(async (mymovies) => {
    // action on passed value of mymovies
}, []);

:调用代码从 onclick = {myFunction} onclick = {()=&gt; myfunction(电影)}

I would like to add to what @aprillion and @shubham-khatri suggested.

@shubham-khatri suggested this:

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

right ?

But my code was like this (myfunction being triggered on a click event)

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

This above code was still not working. Note the difference, the difference is I have async. In case you have async the code will not wait for updated movies irrespective of [movies] , so you will have to do like this:

const myfunction = useEffect(async (mymovies) => {
    // action on passed value of mymovies
}, []);

and the calling code changed from onClick={myfunction} to onClick={() => myfunction(movies)}

你与昨日 2025-02-18 02:05:32

由于usestate是异步直接替换状态不是一个好主意。因此,最好访问先前的状态并设置新的更新值。

for ex:setState(((prev)=&gt; [... prev,... result]);
//结果是对象数组

Since useState is async direct replacement of state is not a good idea. so it is better to access the previous state and set new updated value.

for ex: setState((prev)=>[...prev, ...result]);
//result is array of objects

老旧海报 2025-02-18 02:05:32

使用我库的自定义挂钩,您可以等待状态值更新:

  1. useasyncwatcher(...值):watcherfn(peekprevvalue:boolean:boolean)=&gt; promise - 是围绕使用effereff的承诺包装器如果可选的 peekprevvalue 参数设置为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 是一个深度的状态实现(类似于以下。setState(PatchObject)),其设置器可以返回与内部效果同步的承诺。如果没有参数调用设置器,则不会更改状态值,而只是订阅状态更新。在这种情况下,您可以从组件内的任何地方获得状态值,因为功能关闭不再是障碍。

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>
  );
}

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>
  );
}
夏末染殇 2025-02-18 02:05:32
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.
})
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.
})
指尖凝香 2025-02-18 02:05:32

没有任何附加的NPM软件包

//...
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>
        </>
    )

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>
        </>
    )
待"谢繁草 2025-02-18 02:05:32

⚠️功能组件

我认为,一种超级干净的方法是创建一个自定义挂钩,它提供了将回调传回设置器函数的能力,那么,在此之后精确地采取某些操作将是100%保证国家的更新。

通过查看这篇文章您可以理解如何制作 usestatecallback hook。通过使用 usestatecallback 来定义状态,就像以下内容:

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
  })
};

⚠️ 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
  })
};
默嘫て 2025-02-18 02:05:32

问题

我的问题并没有真正试图在调用设定方法后立即访问该州。在Rerender发生后,我试图以完全不同的函数来执行此操作,但是更新仍未得到反映。目标函数是在功能组件中定义的,但是从类组件中调用的。

就我而言,我最终意识到这是由陈旧关闭引起的问题。这可能是因为类组件不使用 usestate 功能,因此我的代码中的类组件将函数传递给了它并创建了它或其他东西,并且该副本并未使用最多的副本 - 至今引用我的变量。直接传递给类组件的实际变量仍然适当地反映了。

用功能组件代替类组件的解决方案

为我解决了问题。

Issue

My issue wasn't really trying to access the state right away after calling the set method. I was attempting to do it in a completely different function after the rerender had happened but the update still wasn't being reflected. The target function was defined in a functional component but it was being called from a class component.

In my case, I ended up realizing that it was an issue caused by a stale closure. This was likely happening because class components do not use useState functionality and so the class component in my code took the function passed to it and created a copy of it or something and that copy was not using the most up-to-date reference to my variable. Actual variables passed directly to the class component still got reflected properly though.

Solution

Replacing the class component with a functional component solved the issue for me.

被翻牌 2025-02-18 02:05:32

您可以尝试将使用效果挂钩与包含iSopen的依赖项数组一起使用,每次iSopen状态更改时都会执行回调函数,从而确保Console.log(Isopen)记录更新的状态值。这样,您可以正确处理状态更新并确保UI反映组件的当前状态。

You can try to use the useEffect hook with a dependency array containing isOpen, the callback function will be executed every time the isOpen state changes, ensuring that console.log(isOpen) logs the updated state value. This way, you can correctly handle state updates and ensure that your UI reflects the current state of your component.

冰雪梦之恋 2025-02-18 02:05:32

并不是说要这样做,但是在没有使用效果的情况下做OP要求的事情并不难。

使用承诺在设置器函数的正文中解析新状态:

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

这就是您的使用方式(示例显示了 Count OUTOFSYNCCOUNT /之间的比较UI渲染中的Synccount ):

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>
    </>
  );
};

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>
    </>
  );
};
两个我 2025-02-18 02:05:32
// replace
return <p>hello</p>;
// with
return <p>{JSON.stringify(movies)}</p>;

现在,您应该看到,您的代码实际上确实有效。不起作用的是 console.log(Mission)。这是因为电影指向旧状态。如果您将 console.log(电影)移动使用,就在返回上方,您将看到更新的电影对象。

// 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.

嗳卜坏 2025-02-18 02:05:32

使用背景计时器库。它解决了我的问题。

const timeoutId = BackgroundTimer.setTimeout(() => {
    // This will be executed once after 1 seconds
    // even when the application is the background
    console.log('tac');
}, 1000);

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);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文