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

问题描述

这是我的第一个 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

标签: reactjs

解决方案


问题在于闭包,因为 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
        }));
    }
}

推荐阅读