这是我的第一个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
答案 0 :(得分:1)
问题是封闭,因为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
}));
}
}