next.js+ SSR不更新上下文API

发布于 2025-02-13 04:34:29 字数 2222 浏览 1 评论 0 原文

我们有一个简单的设置,涉及上下文API提供商包装 _APP.TSX 页面。我们正在尝试实现的流量是:

  1. 在页面上,我们使用 getserversideprops 我们从API收集一些数据,
  2. 我们通过道具将数据发送到页面,
  3. 我们从页面上更新上下文(上下文提供者正在包装 _APP.TSX ,如上所述)
  4. 上下文已更新,所有子女组件都可以访问其中的数据,

因此我们有一个非常标准的设置,可以在客户端端正确工作。问题在于,更新的上下文值没有用于SSR页面。

上下文API

const ContextValue = createContext();
const ContextUpdate = createContext();

export const ContextProvider = ({children}) => {
  const [context, setContext] = useState({});
  
  return <ContextValue.Provider value={context}>
    <ContextUpdate.Provider value={setContext}>
      {children}
    </ContextValue.Provider>
  </ContextUpdate.Provider>
}

export const useContextValue = () => {
  const context = useContext(ContextValue);
  return context;
}

export const useContextUpdate = () => {
  const update = useContext(ContextUpdate);
  return update;
}

然后我们在 _App.jsx 上都有上下文:

...
return <ContextProvider>
  <ContextApiConsumingComponent />
  <Component {...pageProps} />
</Context>

在任何页面中,如果我们可以使用上面提供的钩子更新上下文。例如:

export function Index({customData, isSSR}) {
  const update = useContextUpdate();

  if(isSSR) {
    update({customData})
  }
  return (
    <div>...</div>
  );
}

export async function getServerSideProps(context) {
  ...

  return {
      props: { customData , isSSR}
  };
};

我们可以在 contextapiconsumingcomponent 中消耗上下文:(

export function ContextApiConsumingComponent() {
  const context = useContextValue()

  return <pre>{JSON.stringify(context?.customData ?? {})}</pre>
}

非常简化的)代码在客户端上工作正常。但是在SSR期间,如果我们检查服务器发送给浏览器的HTML,我们会注意到&lt; pre&gt;&lt;/pre&gt; 标记即使上下文值正确,发送到 __ next_data __ 脚本部分中的浏览器(加载应用程序后,它们精确地填充了浏览器上的数据)。

我将 github repo 也有最低的再现。

我想念什么吗?

We have a simple setup involving a Context API provider wrapping the _app.tsx page. The flow we're trying to implement is:

  1. On a page we collect some data from an API by using getServerSideProps
  2. We send the data to the page through the props
  3. We update the context from the page (the context provider is wrapping the _app.tsx as mentioned above)
  4. The context is updated and all the children components can access the data in it

So we have a pretty standard setup that works correctly on the client side. The problem is that the updated context values are not being used to SSR the pages.

Context API

const ContextValue = createContext();
const ContextUpdate = createContext();

export const ContextProvider = ({children}) => {
  const [context, setContext] = useState({});
  
  return <ContextValue.Provider value={context}>
    <ContextUpdate.Provider value={setContext}>
      {children}
    </ContextValue.Provider>
  </ContextUpdate.Provider>
}

export const useContextValue = () => {
  const context = useContext(ContextValue);
  return context;
}

export const useContextUpdate = () => {
  const update = useContext(ContextUpdate);
  return update;
}

Then we have on _app.jsx:

...
return <ContextProvider>
  <ContextApiConsumingComponent />
  <Component {...pageProps} />
</Context>

And in any page, if we can update the context by using the hook provided above. For example:

export function Index({customData, isSSR}) {
  const update = useContextUpdate();

  if(isSSR) {
    update({customData})
  }
  return (
    <div>...</div>
  );
}

export async function getServerSideProps(context) {
  ...

  return {
      props: { customData , isSSR}
  };
};

And we can consume the context in our ContextApiConsumingComponent:

export function ContextApiConsumingComponent() {
  const context = useContextValue()

  return <pre>{JSON.stringify(context?.customData ?? {})}</pre>
}

The (very simplified) code above works fine on the client. But during the SSR, if we inspect the HTML sent to the browser by the server, we'll notice that the <pre></pre> tags are empty even though the context values are correctly sent to the browser in the __NEXT_DATA__ script section (they are precisely filled with the data on the browser after the app is loaded).

I've put together a GitHub repo with a minimum reproduction too.

Am I missing something?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

開玄 2025-02-20 04:34:29

据我所知,React不等待在SSR期间执行异步动作。而SetState是一个异步操作。

如果您希望在SSR期间对其进行渲染,则需要为 initialValue “> usestate 钩子。

export const ContextProvider = ({children, customData}) => {
  const [context, setContext] = useState(customData);
  
  // ...
}

As far as I understand, React doesn't wait for asynchronous actions to be performed during the SSR. Whereas setState is an asynchronous operation.

If you want it to be rendered during SSR, you need to provide an initialValue for useState hook.

export const ContextProvider = ({children, customData}) => {
  const [context, setContext] = useState(customData);
  
  // ...
}
青柠芒果 2025-02-20 04:34:29

不确定是否有其他原因使用上下文将此数据获取到API消费者中,但是使用NextJ, getStaticProps getserversideprops 当然要馈入路由组件,但是也许您错过了以下细节?

组件道具是活动页面,因此,每当您在路由之间导航时,组件都会更改为新页面。 因此,您发送给组件的任何道具将由页面

收到

pageProps 是一个对象,其初始道具通过我们的数据获取方法预先加载了您的页面,否则是一个空的对象。

https://nextjs.org/docs/advanced-fanced-features/custom-custom-app

因此, _app.tsx 可以在您的路由组件中采取任何行动,通常会从 getStaticProps getServersideProps

types.ts


// or whatever union type you need to indicate that myriad of "custom datas" you might be pushing out of getStaticProps
export type MaybeCustomDataFromGetStaticProps = Map<string, string> | undefined;

export type PagePropsWithCustomData = {
  customData: MaybeCustomDataFromGetStaticProps
  isSSR?: boolean;
}

pages/_app.tsx

import type { PagePropsWithCustomData } from '../types';

const App = ({
  Component,
  pageProps: { customData, isSSR, ...pageProps},
  router,
}: AppProps<PagePropsWithCustomData & Record<string, unknown>>) => {

  return (
    <ContextProvider value={{ isSSR, customData }}>
      <ContextApiConsumingComponent />
      <Component {...pageProps} />
    </ContextProvider>
  )

}

pages/somepage.tsx

import type { NextPage, GetServerSideProps } from "next";
import type { PagePropsWithCustomData } from '../types';
import { magicallyGetCustomData } from '../services/Api';
import { isSSR } from '../core/Routing';

type IndexRouteProps = PagePropsWithCustomData & {
  isSSR?: boolean;
}

const IndexRoute:NextPage<IndexRouteProps> = ({ customData, isSSR }) => {

  // no longer need to do anything with customData, or isSSR here

  return (
    <div>stuff</div>
  );
}

export const getServerSideProps:GetServerSideProps<IndexRouteProps> = async (context) => {
  const customData = await magicallyGetCustomData();
  
  return {
      props: { customData , isSSR: isSSR(context) }
  };
};

export default IndexRoute

Not sure if you have other reasons for using the context to get this data into the api consumer but with nextjs, the getStaticProps or getServerSideProps of course feeds into the route component, but maybe you've missed the following detail?

The Component prop is the active page, so whenever you navigate between routes, Component will change to the new page. Therefore, any props you send to Component will be received by the page.

pageProps is an object with the initial props that were preloaded for your page by one of our data fetching methods, otherwise it's an empty object.

https://nextjs.org/docs/advanced-features/custom-app

Therefore the _app.tsx can act on any props your route components would normally receive from getStaticProps or getServerSideProps

types.ts


// or whatever union type you need to indicate that myriad of "custom datas" you might be pushing out of getStaticProps
export type MaybeCustomDataFromGetStaticProps = Map<string, string> | undefined;

export type PagePropsWithCustomData = {
  customData: MaybeCustomDataFromGetStaticProps
  isSSR?: boolean;
}

pages/_app.tsx

import type { PagePropsWithCustomData } from '../types';

const App = ({
  Component,
  pageProps: { customData, isSSR, ...pageProps},
  router,
}: AppProps<PagePropsWithCustomData & Record<string, unknown>>) => {

  return (
    <ContextProvider value={{ isSSR, customData }}>
      <ContextApiConsumingComponent />
      <Component {...pageProps} />
    </ContextProvider>
  )

}

pages/somepage.tsx

import type { NextPage, GetServerSideProps } from "next";
import type { PagePropsWithCustomData } from '../types';
import { magicallyGetCustomData } from '../services/Api';
import { isSSR } from '../core/Routing';

type IndexRouteProps = PagePropsWithCustomData & {
  isSSR?: boolean;
}

const IndexRoute:NextPage<IndexRouteProps> = ({ customData, isSSR }) => {

  // no longer need to do anything with customData, or isSSR here

  return (
    <div>stuff</div>
  );
}

export const getServerSideProps:GetServerSideProps<IndexRouteProps> = async (context) => {
  const customData = await magicallyGetCustomData();
  
  return {
      props: { customData , isSSR: isSSR(context) }
  };
};

export default IndexRoute
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文