reactjs - 全局上下文中的值在特定情况下为空,在其他任何地方都不为空
问题描述
这是我的第一个 React 应用程序。我有一个全局上下文,它包含一组对象,它们是发票。全局上下文是这样创建的:
import React, { createContext, useState, useMemo } from 'react';
/**
* Import the initial global state from the #root element,
* which is in views/admin-pages.php
*/
const rootEl = document.getElementById('root');
const initialState = JSON.parse( rootEl.getAttribute('initGlobalState') );
const GlobalContext = createContext();
function GlobalContextProvider({children}){
const [state, setState] = useState(initialState);
const contextValue = useMemo(() => [state, setState], [state]);
return (
<GlobalContext.Provider value={contextValue}>
{children}
</GlobalContext.Provider>
);
}
export { GlobalContext, GlobalContextProvider };
全局上下文提供程序包装了整个应用程序,因此在任何组件中我都可以通过以下方式访问它:
import { GlobalContext } from './../contexts/GlobalContext';
// ...
const [ globalState, setGlobalState ] = useContext(GlobalContext);
由于发票存储在全局上下文中,因此我可以使用globalState.invoices
. 如果我想添加一些发票,我可以这样做:
const appendInvoices = ( invoices ) => {
setGlobalState(globalState => ({
...globalState,
invoices: globalState.invoices.concat(invoices)
}));
}
这总是有效的,但在同一个文件中,我设置了一个超时/间隔来在服务器上查找付费发票:
useEffect(() => {
let paymentsCheck = setTimeout(() => {
setInterval(() => checkForNewPayments(), 60000);
}, 60000);
return () => {
clearTimeout( paymentsCheck );
}
}, []);
当发票已支付需要更新时,paidInvoices 是一个对象,其中键是发票的数字 ID,值是时间戳:
const checkForNewPayments = () => {
invModel.fetchNewPayments( response => {
if(
response.paidInvoices &&
Object.keys(response.paidInvoices).length > 0
){
updatePaymentStatuses(response.paidInvoices);
}
});
}
最后,我正在尝试使用更改来更新 globalState:
const updatePaymentStatuses = ( paidInvoices ) => {
// WHY IS globalState.invoices EMPTY ???
console.log( globalState.invoices );
const newInvoicesArr = globalState.invoices.map( invoice => {
if( invoice.id in paidInvoices )
invoice.paidAt = paidInvoices[invoice.id];
return invoice;
});
if(newInvoicesArr.length > 0){
setGlobalState(globalState => ({
...globalState,
invoices: newInvoicesArr
}));
}
}
问题是,在这个函数中,globalState.invoices 是空的,这是不可能的,因为页面上有发票。如果 globalState.invoices 确实为空,则该页面将不呈现任何发票。
所以,我希望有人会看到我没有看到的东西。我的问题是,为什么 globalState.invoices 显然不是空的?
如果你想查看整个文件,可以在这里找到: https ://bitbucket.org/skunkbad/simple-invoicing-w-stripe-wp-plugin/src/master/wp-content/plugins/simple-invoicing- w-stripe/admin-app/src/components/InvoicesTab.js
解决方案
问题在于闭包,因为 useEffect 仅在初始渲染时调用,您的updatePaymentStatuses
方法从内部调用useEffect
试图访问的 globalState 对象将始终引用初始值,因为它在创建时从其词法范围获取值.
有几个解决方案
首先,由于您在 useEffect 中使用 globalState,因此必须将其传递给依赖数组,useEffect
以便在全局状态更新时关闭效果,例如
useEffect(() => {
let paymentsCheck = setTimeout(() => {
setInterval(() => checkForNewPayments(), 60000);
}, 60000);
return () => {
clearTimeout( paymentsCheck );
}
}, [globalState]);
但是,如果您在 useEffect 中设置状态并且您正在观察相同的状态,这始终不是一个好主意。这可能会导致无限循环
第二种解决方案是保留使用的globalState
参考useRef
const [ globalState, setGlobalState ] = useContext(GlobalContext);
const stateRef = useRef(globalState);
useEffect(() => {
stateRef.current = globalState;
}, [globalState]);
useEffect(() => {
let paymentsCheck = setTimeout(() => {
setInterval(() => checkForNewPayments(), 60000);
}, 60000);
return () => {
clearTimeout( paymentsCheck );
}
}, []);
const updatePaymentStatuses = ( paidInvoices ) => {
console.log( stateRef.current.invoices ); // use globalState from ref
const newInvoicesArr = stateRef.current.invoices.map( invoice => {
if( invoice.id in paidInvoices )
invoice.paidAt = paidInvoices[invoice.id];
return invoice;
});
if(newInvoicesArr.length > 0){
setGlobalState(globalState => ({
...globalState,
invoices: newInvoicesArr
}));
}
}
推荐阅读
- sql - Vertica Analytic 函数用于计算窗口中的实例数
- ios - 使用 MapKit 时在创建时设置 MKPointAnnotation 的标题
- python - 我可以使用 python 来切换 Windows 10 通知吗?
- r - 从非 highmap-collection 地图制作等值线
- c++ - 如何将属性页添加到 CPrintDialogEx
- php - 插入只做第一行
- c# - C# 将 Active Directory 十六进制转换为 GUID
- python - 如何计算熊猫数据框中成对数据的出现次数?
- c# - 将字符串数组传递给方法并显示结果(WPF、C#)
- c# - UWP 程序创建可供其他用户访问的网络共享文件夹的最佳方式是什么?