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.
与通过扩展React.Component或React.PureComponent创建的类组件中的.setState()非常类似,使用useState钩子提供的更新器进行的状态更新也是异步的,不会立即反映出来。
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.
即使添加了setTimeout函数,虽然超时将在重新渲染发生的一段时间后运行,但setTimeout仍将使用其上一个关闭的值,而不是更新的值。
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挂钩,这与在类组件中使用ComponentDidUpdate非常相似,因为useState返回的setter没有回调模式
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.
就更新状态的语法而言,setMovies(Result)将用来自异步请求的值替换状态中以前的电影值。
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
但是,如果要将响应与以前存在的值合并,则必须使用STATE UPDATION的回调语法以及正确使用扩展语法,如
setMovies(prevMovies => ([...prevMovies, ...result]));
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.
虽然React的setState是异步的(类和钩子都是),并且很容易使用这一事实来解释观察到的行为,但这并不是它发生的原因。
TLDR: The reason is a closure scope around an immutable const
value.
TLDR:原因是围绕不可变的常量值的闭包作用域。
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):
将变量添加到依赖项中(并使用REACTIVE-HOOKS/EXPIRECTIVE-DEPS eslint规则):
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()
.
如果Async是唯一的原因,则可以等待setState()。
However, both props
and state
are assumed to be unchanging during 1 render.
然而,道具和状态在1次渲染期间都被假定为不变。
Treat this.state
as if it were immutable.
With hooks, this assumption is enhanced by using constant values with the const
keyword:
对于挂钩,通过将常量值与const关键字一起使用来增强这一假设:
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).
该值在两个render之间可能不同,但在render本身和任何闭包(即使在render完成后仍然存在更长时间的函数,例如useEffect,事件处理程序,在任何Promise或setObject中)中保持不变。
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>
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.
我知道已经有了非常好的答案。但我想给出另一个想法如何解决同样的问题,并访问最新的‘电影’状态,使用我的模块Reaction-useStateRef它有11,000多周的下载量。
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.
正如您所了解的,通过使用Reaction状态,您可以在每次状态更改时呈现页面。但通过使用REACTION REF,您始终可以获得最新的值。
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
因此,模块Reaction-useStateRef允许您同时使用状态和引用。它向后兼容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);
}
})();
}, []);
More information:
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.
Reaction的useEffect有自己的状态/生命周期。它与状态的突变有关,在效果被摧毁之前,它不会更新状态。
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.
或者,您可以尝试使用React.useRef()在Reaction挂钩中进行即时更改。
const movies = React.useRef(null);
useEffect(() => {
movies.current='values';
console.log(movies.current)
}, [])
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.
我刚刚用useReducer完成了一次重写,遵循了Kent C.Dodds的文章(参考下文),这篇文章确实给了我一个可靠的结果,丝毫没有受到这些闭包问题的影响。
See: https://kentcdodds.com/blog/how-to-use-react-context-effectively
参见: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
现在在我所有的页面上,一切都很顺利
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.
基于useState的源代码(简化如下)。在我看来,价值从来不是立即分配的。
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.
发生的情况是,当您调用setValue时,更新操作将排队。在计划开始后,仅当您到达下一次渲染时,这些更新操作才会应用于该状态。
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.
这意味着即使我们没有闭包问题,REACT版本的useState也不会立即为您提供新值。新值甚至在下一次渲染之前都不存在。
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
也有一篇文章以类似的方式解释了以上内容,https://dev.to/adamklein/we-don-t-know-how-react-state-hook-works-1lp8
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.
我也遇到了同样的问题。正如上面的其他答案已经澄清了这里的错误,即useState是异步的,您试图在setState之后使用该值。由于setState的异步特性,它不会在console.log()部分更新,它允许您执行进一步的代码,而值更新发生在后台。这样你就得到了以前的值。当setState在后台完成时,它将更新值,您将在下一次渲染时访问该值。
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
https://www.youtube.com/watch? v=8aGhZQkoFbQ
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.
当一个触发特定代码的事件发生时,代码开始运行,当它结束时,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>
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:
假设我们有一个场景,其中我们有一个依赖于另一个状态的状态,例如,我们希望在每次更新时基于Example的新值进行API调用,然后将来自Response的数据存储在另一个状态中。要做到这一点,我们有两种方法:
1. use the value of newValue
:
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>
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:
2.每次更新示例时,通过将Example包含在其依赖数组中,触发useEffect运行:
<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.
因此,当使用组件重新呈现的事件函数更新Example时,我们现在处于一个新的不同渲染中,一旦完成,useEffect将运行,因为Example的值不同于上次渲染期间的值,而且由于它是新的不同渲染,因此Example useState钩子的新值在此处可用。
Note: the useEffect
hook will run anyway during the first mount.
注意:在第一次挂载期间,useEffect挂钩无论如何都会运行。
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
😕
虽然第一种方法将在一个渲染中完成所有工作(更好的方法)“React将多个状态更新分组到一个重新渲染中以获得更好的性能”,但第二种方法将在两个渲染中完成,第一个是在example更新时,第二个是在另一个example从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 😕
由于组件只在useState钩子的新值与旧的值不同时重新呈现,因此当newValue等于Example时,组件将不会重新呈现,因此useEffect将不会运行,并且另一个Example将不会被更新🙂(更好的方法),但是在第一个方法中,无论如何都会调用API,如果不需要这样做,我们不想这样做,如果发生这种情况,另一个Example将被更新(另一个Example将收到它已经包含的相同数据,因为它是相同的请求,因为NewValue与Example相同),但是如果对象或数组中的响应,则对象是方法(useState钩子利用zs),无法检测新值是否等于前一个值,因此,组件将重新呈现😕
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
第二种方法更值得推荐,但在某些情况下,第一种方法的性能可能更好,例如,当您确定代码只有在newValue使用onChange获得新值时才会运行,或者当您想要使用某些其他局部变量时,您将无法再从useEffect内部访问这些变量
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.
React中的useState挂钩返回的setState函数不会立即更新状态。相反,它计划在下一个渲染周期中处理状态更新。这是因为出于性能原因,Reaction会对状态更新进行批处理。
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.
如果您试图在调用setState之后立即访问更新的状态,则可能不会立即看到更新值。相反,您可以使用useEffect挂钩在更新状态后执行操作。
Here's an example to demonstrate how you can use useEffect to perform an action after the state update
下面的示例演示如何使用useEffect在状态更新后执行操作
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.
在上面的示例中,useEffect挂钩用于在每次状态更新后记录更新后的计数值。通过将[count]作为依赖项数组传递到useEffect,该效果将仅在计数状态更改时运行。
I found this to be good. Instead of defining state (approach 1) as, example,
我觉得这很不错。代替将状态(方法1)定义为,例如,
const initialValue = 1;
const [state,setState] = useState(initialValue)
Try this approach (approach 2),
尝试这种方法(方法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.
这在不使用useEffect的情况下解决了再渲染问题,因为我们不关心它的内部关闭方法。
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.
附注:如果您关心在任何用例中使用旧状态,那么需要使用带有useEffect的useState,因为它将需要具有该状态,所以在这种情况下应该使用方法1。
If we have to update state only, then a better way can be if we use the push method to do so.
如果我们只需要更新状态,那么更好的方法是使用Push方法进行更新。
Here is my code. I want to store URLs from Firebase in state.
这是我的代码。我想存储来自州的Firebase的URL。
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.
这段代码将URL作为数组放入状态。这对你来说可能也行得通。
With custom hooks from my library, you can wait for the state values to update:
使用我的库中的定制挂钩,您可以等待状态值更新:
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;
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>
);
}
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.
})
Without any addtional NPM package
不需要任何额外的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>
</>
)
⚠️ 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.
我认为一个非常干净的方法是创建一个定制的钩子,它提供了将回调传递给setter函数的能力,那么它将100%保证在状态更新之后执行一些操作。
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:
通过阅读这篇文章,您可以理解如何创建useStateCallback挂钩。使用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
})
};
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.
我的问题并不是在调用set方法后立即尝试访问状态。在重新渲染发生后,我试图在一个完全不同的函数中进行更新,但更新仍然没有反映出来。目标函数是在功能组件中定义的,但它是从类组件调用的。
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.
在我的例子中,我最终意识到这是一个由陈旧的关闭引起的问题。这很可能是因为类组件不使用useState功能,所以代码中的类组件接受传递给它的函数并创建它的副本或其他东西,而该副本没有使用对我的变量的最新引用。但是,直接传递给类组件的实际变量仍然得到了正确的反映。
Solution
Replacing the class component with a functional component solved the issue for me.
用功能组件替换类组件为我解决了这个问题。
Not saying to do this, but it isn't hard to do what the OP asked without useEffect.
并不是说要这样做,但在不使用Effect的情况下执行OP要求的操作并不难。
Use a promise to resolve the new state in the body of the setter function:
在setter函数体中使用Promise来解析新状态:
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):
下面是它的使用方法(示例显示了UI渲染中count和outOfSyncCount/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>
</>
);
};
// 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.
现在您应该看到,您的代码实际上是可以工作的。不起作用的是console.log(电影)。这是因为电影指向的是旧国家。如果将console.log(Movies)移到useEffect之外,就在返回的正上方,您将看到更新的Movies对象。
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);
更多回答
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 !
嗨,在表单提交处理程序中调用useState怎么样?我正在验证一个复杂的表单,并且我在submitHandler内部调用了useState钩子,不幸的是,更改不是立即进行的!
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.
但是,useEffect可能不是最好的解决方案,因为它不支持异步调用。因此,如果我们想要对电影状态更改进行一些异步验证,我们无法控制它。
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
请注意,虽然建议非常好,但对原因的解释是可以改进的-与useState挂钩提供的更新程序是否是异步的事实无关,不同于此。如果this.setState是同步的,则可能已经发生了变化的状态,即使useState提供了同步函数,const电影周围的闭包也将保持不变-请参见我答案中的示例
setMovies(prevMovies => ([...prevMovies, ...result]));
worked for me
SetMovies(PrevMovies=>([...PrevMovies,...Result]));为我工作
It is logging the wrong result because you are logging a stale closure 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.
它记录错误的结果是因为您记录的是过时的闭包,而不是因为setter是异步的。如果问题出在异步,那么您可以在超时后登录,但您可以将超时设置为一个小时,但仍然会记录错误的结果,因为导致问题的不是异步。
@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...
@AlJoslin乍一看,这似乎是另一个问题,即使它可能是由闭包范围引起的。如果你有一个具体的问题,请创建一个新的堆栈溢出问题与代码示例和所有…
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: kentcdodds.com/blog/how-to-use-react-context-effectively)
实际上,我刚刚用useReducer完成了一个重写,在@kentcdobs文章(参考下面)之后,这真的给了我一个坚实的结果,没有一点受到这些闭包问题的影响。(ref:kentcdodds.com/blog/how-to-use-react-context-effectively)
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 ..
由于某种原因,解决方案2不起作用。我收到了回调,但值仍然为空。UseEffect(()=>{console.log(Movies)},[Movies])此命令不打印任何内容..
@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.
@ACV解决方案2适用于原始问题。如果您需要解决不同的问题,YMMW,但我仍然100%确定引用的代码可以按照文档说明工作,问题出在其他地方。
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.
所有这些解决方案都需要使用useEffect。我的问题是,我的“Movies”等效物是我从上下文提供程序中获得的对象,并且可以由许多其他组件更改。我不想每次更改效果时都运行它,因为我的效果不是setMovies-它是一个不同的函数,只有在对电影进行特定更改时才需要调用-由于过时的上下文,当需要时我看不到这种更改。
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
这只是第一个需要的
I don't think this answer is particularly helpful in the context of the OP. This answer is not even using useState()
which was central to the OP's inquiry.
我不认为这个答案在行动的背景下特别有帮助。这个答案甚至没有使用对于OP的查询至关重要的useState()。
Smooth solution, but not an answer to what was going on
顺利的解决方案,但不是对发生的事情的答案
The useState
was always asynchronous since the first day, it's not related to ReactJs 18 version.
从第一天开始,useState就一直是异步的,它与ReactJs18版本无关。
I didnt say it is a new feature in React 18.
我没有说这是Reaction 18中的一个新功能。
So why you mentioned the Reactjs version? it confused me
那么为什么你提到了Reactjs版本呢?这让我很困惑
because this answer is posted when React 18 is the last released version, maybe later it won't be valid, who knows
因为这个答案是在React 18是最后一个发布版本时发布的,也许以后它就无效了,谁知道呢
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.
这个答案没有用。对于重新呈现和捕获的闭合值,这种方法没有丝毫不同。当状态值被故意设置为未定义时,它所产生的唯一区别是,在这种情况下,您将再次获得初始值。这只是一种令人困惑的方法,因为您可以只将其设置为初始值,而不需要额外的步骤。
Approach 2 pollutes the global space. Approach 1, as mentioned, is an antipattern at best.
方法2污染了全球空间。如前所述,方法1充其量是一种反模式。
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.
在循环中调用setImageUrl是另一个糟糕的做法,这将在每次调用它时触发一个新的呈现,因为在异步调用时(在React的生命周期之外)不会对其进行批处理。正确的方法是构建新数组,然后只调用一次setImageUrl。
Don't do that. setState
's setter callback should be pure. Also, here, updatedState
would always be undefined
.
别干那事。SetState的setter回调应该是纯的。此外,在这里,updatdState将始终是未定义的。
@EmileBergeron do you have a link to the documentation stating that the callbacks should be free of side effects?
@Emily Bergeron你有没有文档的链接,说明回调应该没有副作用?
I dont have the link on hand, but it's documented alongside strict mode, which helps identify unwanted side-effects.
我手头上没有链接,但它是与严格模式一起记录的,这有助于识别不想要的副作用。
setState
setter callback should be pure.
SetState setter回调应为纯回调。
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
我赞成强调纯函数,但该链接并不是特定于useState回调的(有不使用纯函数作为回调的用例)。此外,尽管这不是“纯粹的”,但它实际上也不会改变任何状态。我并不是说这是最好的方法(我不使用它),只是说它为运营问题提供了另一种解决方案
Using count and or callbacks hinders the design method behind usestate itself I'm afraid.
使用count和or回调恐怕会阻碍usEstate本身背后的设计方法。
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
再说一次,不是说这是最好的方法,也不是使用它,只是说它有效。与从设计理论层面忽视它相比,用一个可重复使用的例子来说明使用它会有什么问题会更有用
"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).
“设计理论水平”似乎是批评某事的一个完全合理的理由。仅仅因为某件事有效或有可能,并不意味着它不值得去做。尽管有这些警告,人们实际上可能会使用这种模式。有更好的方法来处理“问题”(这从一开始就不是问题,只是对于那些不习惯使用异步代码的人来说似乎是这样)。
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
不确定为什么这个答案被大量否决,它告诉我们如何通过将其放在useState函数之外来获得“预期的”console.log值。简单而甜蜜,如果有人想知道为什么会发生这样的事情,可以参考上面的详细说明
Good try though
不过,这是一次不错的尝试
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
增加延迟并不是一个实际的解决方案。这只是一种变通办法..即使这样,当您只需使用简单的setTimeout时,也不需要库
我是一名优秀的程序员,十分优秀!