rod*_*rod 7 javascript reactjs react-hooks
I am using React hooks and trying to read state from within a callback. Every time the callback accesses it, it's back at its default value.
With the following code. The console will keep printing Count is: 0 no matter how many times I click.
function Card(title) {
const [count, setCount] = React.useState(0)
const [callbackSetup, setCallbackSetup] = React.useState(false)
function setupConsoleCallback(callback) {
console.log("Setting up callback")
setInterval(callback, 3000)
}
function clickHandler() {
setCount(count+1);
if (!callbackSetup) {
setupConsoleCallback(() => {console.log(`Count is: ${count}`)})
setCallbackSetup(true)
}
}
return (<div>
Active count {count} <br/>
<button onClick={clickHandler}>Increment</button>
</div>);
}
const el = document.querySelector("#root");
ReactDOM.render(<Card title='Example Component' />, el);
Run Code Online (Sandbox Code Playgroud)
You can find this code here
I've had no problem setting state within a callback, only in accessing the latest state.
If I was to take a guess, I'd think that any change of state creates a new instance of the Card function. And that the callback is referring to the old one. Based on the documentation at https://reactjs.org/docs/hooks-reference.html#functional-updates, I had an idea to take the approach of calling setState in the callback, and passing a function to setState, to see if I could access the current state from within setState. Replacing
setupConsoleCallback(() => {console.log(`Count is: ${count}`)})
Run Code Online (Sandbox Code Playgroud)
with
setupConsoleCallback(() => {setCount(prevCount => {console.log(`Count is: ${prevCount}`); return prevCount})})
Run Code Online (Sandbox Code Playgroud)
You can find this code here
That approach hasn't worked either. EDIT: Actually that second approach does work. I just had a typo in my callback. This is the correct approach. I need to call setState to access the previous state. Even though I have no intention of setting the state.
I feel like I've taken similar approaches with React classes, but. For code consistency, I need to stick with React Effects.
How can I access the latest state information from within a callback?
Bra*_*don 93
对于您的场景(您无法继续创建新的回调并将它们传递给您的 3rd 方库),您可以使用 useRef保持一个具有当前状态的可变对象。像这样:
function Card(title) {
const [count, setCount] = React.useState(0)
const [callbackSetup, setCallbackSetup] = React.useState(false)
const stateRef = useRef();
// make stateRef always have the current count
// your "fixed" callbacks can refer to this object whenever
// they need the current value. Note: the callbacks will not
// be reactive - they will not re-run the instant state changes,
// but they *will* see the current value whenever they do run
stateRef.current = count;
function setupConsoleCallback(callback) {
console.log("Setting up callback")
setInterval(callback, 3000)
}
function clickHandler() {
setCount(count+1);
if (!callbackSetup) {
setupConsoleCallback(() => {console.log(`Count is: ${stateRef.current}`)})
setCallbackSetup(true)
}
}
return (<div>
Active count {count} <br/>
<button onClick={clickHandler}>Increment</button>
</div>);
}
Run Code Online (Sandbox Code Playgroud)
您的回调可以引用可变对象来“读取”当前状态。它将在其闭包中捕获可变对象,并且每次渲染可变对象都将使用当前状态值进行更新。
Ami*_*ein 28
使用 NPM 模块react-usestateref始终获取最新的状态值。它与 React useStateAPI完全向后兼容。
示例代码如何使用它:
import useState from 'react-usestateref';
const [count, setCount, counterRef] = useState(0);
console.log(couterRef.current); // it will always have the latest state value
setCount(20);
console.log(counterRef.current);
Run Code Online (Sandbox Code Playgroud)
NPM 包react-useStateRef允许您ref使用useState.
为了完全解决这个问题,我为此创建了一个反应模块。react-usestateref(反应 useStateRef)。例如使用:
import useState from 'react-usestateref';
const [count, setCount, counterRef] = useState(0);
console.log(couterRef.current); // it will always have the latest state value
setCount(20);
console.log(counterRef.current);
Run Code Online (Sandbox Code Playgroud)
它的工作原理很像,useState但此外,它还为您提供了当前状态ref.current
了解更多:
您可以使用 setState
例如:
var [state, setState, ref] = useState(0);
Run Code Online (Sandbox Code Playgroud)
dav*_*wil 14
我遇到了一个类似的错误,试图做与您在示例中所做的完全相同的事情 -setInterval在引用props或state来自 React 组件的回调上使用。
希望我可以通过从稍微不同的方向解决问题来补充已经在这里的好答案 - 意识到它甚至不是 React 问题,而是一个简单的旧 Javascript 问题。
我认为这里最引人注目的是 React hooks 模型的思考,其中状态变量,毕竟只是一个局部变量,可以被视为在 React 组件的上下文中它是有状态的。您可以确定,在运行时,变量的值将始终是 React 为该特定状态保存的任何内容。
然而,一旦你脱离 React 组件上下文——setInterval例如在 a 内部的函数中使用变量,抽象就会中断,你又回到了这个事实,即状态变量实际上只是一个保存值的局部变量。
抽象允许您编写代码,就好像运行时的值将始终反映状态一样。在 React 的上下文中,情况就是这样,因为每当您设置状态时,整个函数都会再次运行,并且变量的值由 React 设置为更新后的状态值。然而,在回调内部,不会发生这样的事情——该变量不会神奇地更新以反映调用时的底层 React 状态值。它就是定义回调时的样子(在本例中0),并且永远不会改变。
这是我们得到解决方案的地方:如果该局部变量指向的值实际上是对可变对象的引用,那么事情就会改变。的值(其为参考)保持在堆栈上恒定的,而是通过将其在堆上引用的可变的值(一个或多个)可被改变。
这就是已接受答案中的技术有效的原因 - React ref 提供了对可变对象的这种引用。但我认为必须强调的是,其中的“React”部分只是巧合。解决方案,就像问题一样,本身与 React 无关,只是 React ref 恰好是获取对可变对象的引用的一种方式。
例如,您还可以使用一个普通的 Javascript 类,在 React 状态下保存它的引用。需要明确的是,我并不是在暗示这是一个更好的解决方案,甚至不是可取的(它可能不是!),只是用它来说明这个解决方案没有“反应”方面的观点——它只是 Javascript:
class Count {
constructor (val) { this.val = val }
get () { return this.val }
update (val) {
this.val += val
return this
}
}
function Card(title) {
const [count, setCount] = React.useState(new Count(0))
const [callbackSetup, setCallbackSetup] = React.useState(false)
function setupConsoleCallback(callback) {
console.log("Setting up callback")
setInterval(callback, 3000)
}
function clickHandler() {
setCount(count.update(1));
if (!callbackSetup) {
setupConsoleCallback(() => {console.log(`Count is: ${count.get()}`)})
setCallbackSetup(true)
}
}
return (
<div>
Active count {count.get()} <br/>
<button onClick={clickHandler}>Increment</button>
</div>
)
}
const el = document.querySelector("#root");
ReactDOM.render(<Card title='Example Component' />, el);
Run Code Online (Sandbox Code Playgroud)
你可以看到,只要让 state 指向一个引用,它不会改变,并且改变引用指向的底层值,你就会在setInterval闭包和 React 组件中获得你所追求的行为。
同样,这不是惯用的 React,只是说明了引用是这里的最终问题。希望它有帮助!
Nea*_*arl 10
state您可以在回调中获取最新消息setState。但意图并不明确,我们永远不想setState在这种情况下这样做,这可能会让其他人在阅读你的代码时感到困惑。所以你可能想把它包装在另一个钩子中,这样可以更好地表达你想要的东西
function useExtendedState<T>(initialState: T) {
const [state, setState] = React.useState<T>(initialState);
const getLatestState = () => {
return new Promise<T>((resolve, reject) => {
setState((s) => {
resolve(s);
return s;
});
});
};
return [state, setState, getLatestState] as const;
}
Run Code Online (Sandbox Code Playgroud)
const [counter, setCounter, getCounter] = useExtendedState(0);
...
getCounter().then((counter) => /* ... */)
// you can also use await in async callback
const counter = await getCounter();
Run Code Online (Sandbox Code Playgroud)
不要尝试访问回调中的最新状态,而是使用useEffect. 使用返回的函数设置您的状态setState不会立即更新您的值。状态更新是批量更新的
如果您想到useEffect()likesetState的第二个参数(来自基于类的组件),这可能会有所帮助。
如果你想对最近的状态进行操作,请使用useEffect()状态改变时会发生的操作:
const {
useState,
useEffect
} = React;
function App() {
const [count, setCount] = useState(0);
const decrement = () => setCount(count-1);
const increment = () => setCount(count+1);
useEffect(() => {
console.log("useEffect", count);
}, [count]);
console.log("render", count);
return (
<div className="App">
<p>{count}</p>
<button onClick={decrement}>-</button>
<button onClick={increment}>+</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render( < App / > , rootElement);Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>Run Code Online (Sandbox Code Playgroud)
更新
您可以为您创建一个钩子setInterval并像这样调用它:
const {
useState,
useEffect,
useRef
} = React;
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
function Card(title) {
const [count, setCount] = useState(0);
const callbackFunction = () => {
console.log(count);
};
useInterval(callbackFunction, 3000);
useEffect(()=>{
console.log('Count has been updated!');
}, [count]);
return (<div>
Active count {count} <br/>
<button onClick={()=>setCount(count+1)}>Increment</button>
</div>);
}
const el = document.querySelector("#root");
ReactDOM.render(<Card title='Example Component'/>, el);Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
116 次 |
| 最近记录: |