子组件不可用的 React 上下文的异步值问题

Issue with async value for React context not available from child component

我正在尝试使用从 js promise 填充的 React 上下文

我遇到的问题是,使用上下文的子组件(即 AnotherPage)在上下文中的值可从根 [=13= 中调用的 useEffect 获得之前呈现] 页。因此,如果我尝试访问其中一个上下文属性,我将收到错误消息,因为它是 undefined/null.

我如何 refactor/redesign 我的应用程序才能从子组件获得上下文?

这是我的应用程序的代码:

import React from 'react';
import { createContext, useEffect, useState, useContext } from 'react';
import './style.css';

const MyContext = createContext(null);

export default function App() {
  console.log('App');

  const [myObj, setMyObj] = useState();

  useEffect(() => {
    Promise.resolve({ id: 42, text: 'some value from promise' }).then((res) => {
      console.log('resolved: ', res);
      setMyObj(res);
    });
  }, []);

  return (
    <MyContext.Provider value={myObj}>
      <div>
        <h1>Hello StackBlitz!</h1>
        <AnotherPage />
      </div>
    </MyContext.Provider>
  );
}

const AnotherPage = () => {
  console.log('AnotherPage');

  const obj = useContext(MyContext);
  console.log('myObj', obj);// undefined/null at first page render...

  return <div>Hello from another page</div>;
};

这里是 Stackblitz:https://stackblitz.com/edit/react-xkxhxk?file=src/App.js

仅供参考,在我的真实应用程序中,从承诺中解析的实际对象比下面的 myObj 复杂得多。

您可以使用一些临时值而不是null来初始化上下文。

而不是做

const MyContext = createContext(null);

const MyContext = createContext({id: -1, text: 'yet to be initialized'});

因此您的子组件将使用这些值,直到 promise 更新上下文值。

我会这样解决:

const ASYNC_STATUS = {
  Idle: 'idle',
  Pending: 'pending',
  Success: 'success',
  Failed: 'failed',
};

export default function App() {
  console.log('App');

  const [myObj, setMyObj] = useState({
    value: null,
    status: ASYNC_STATUS.Idle,
  });

  useEffect(() => {
    try {
      setMyObj((prev) => ({
        ...prev,
        status: ASYNC_STATUS.Pending,
      }));
      Promise.resolve({ id: 42, text: 'some value from promise' }).then(
        (res) => {
          console.log('resolved: ', res);
          setMyObj({
            value: res,
            status: ASYNC_STATUS.Success,
          });
        }
      );
    } catch {
      setMyObj((prev) => ({
        ...prev,
        status: ASYNC_STATUS.Success,
      }));
    }
  }, []);

  return (
    <MyContext.Provider value={myObj}>
      <div>
        <h1>Hello StackBlitz!</h1>
        <AnotherPage />
      </div>
    </MyContext.Provider>
  );
}

const AnotherPage = () => {
  console.log('AnotherPage');

  const { value, status } = useContext(MyContext);
  console.log('myObj', value);

  return (
    <div>
      {status === ASYNC_STATUS.Pending && <div>Loading...</div>}
      {status === ASYNC_STATUS.Failed && <div>Failed!</div>}
      {status === ASYNC_STATUS.Success && value && <div>here what you want to show</div>}
    </div>
  );
};

你无法逃避它,你必须在某处检查是否为空:

  1. 在每个消费者 (useContext) 用户中,最简单的方法是编写自定义挂钩并检查那里的可空性。
  2. 或者,在提供者内部,有条件地呈现子项

请注意,这不是“首次渲染”问题,因为这是一个承诺,您可以将其延迟足够长的时间,以便在首次渲染后更新。

export default function App() {
  const [myObj, setMyObj] = useState();

  useEffect(() => {
    Promise.resolve({ id: 42, text: 'some value from promise' }).then((res) => {
      setTimeout(() => {
        setMyObj(res);
      }, 2000);
    });
  }, []);

  const children = useMemo(
    () => (
      <div>
        <h1>Hello StackBlitz!</h1>
        <AnotherPage />
      </div>
    ),
    []
  );

  return (
    <MyContext.Provider value={myObj}>{myObj && children}</MyContext.Provider>
  );
}

https://stackblitz.com/edit/react-a4s9yr?file=src/App.js

我认为您可以在 promise 解决之前使用加载器。 这样子组件甚至不会在 promise 解析之前被渲染。

import React from 'react';
import { createContext, useEffect, useState, useContext } from 'react';
import './style.css';

const MyContext = createContext(null);

export default function App() {
  console.log('App');

  const [myObj, setMyObj] = useState();

  useEffect(() => {
    Promise.resolve({ id: 42, text: 'some value from promise' }).then((res) => {
      console.log('resolved: ', res);
      setMyObj(res);
    });
  }, []);

  return (
    <MyContext.Provider value={myObj}>
      {
        myObj ? <div>
          <h1>Hello StackBlitz!</h1>
          <AnotherPage />
        </div> : <div>Loading....</div>
      }
    </MyContext.Provider>
  );
}

const AnotherPage = () => {
  console.log('AnotherPage');

  const obj = useContext(MyContext);
  console.log('myObj', obj);// undefined/null at first page render...

  return <div>Hello from another page</div>;
};