全局上下文中的值在特定情况下为空,其他任何地方都不为空

时间:2019-05-17 05:58:03

标签: 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

1 个答案:

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