Nextjs 和上下文 API (Nextjs and Context API)


問題描述

Nextjs 和上下文 API (Nextjs and Context API)

使用 Next.js,我試圖在 getInitialProps 中獲取數據後將數據保存在 Context API 狀態中,以修復道具鑽孔。

但由於 getInitialProps 是一個靜態方法,我們無法通過 this.context 訪問它。我設法將它們保存在 componentDidMount 中,但在這種情況下,在第一頁加載時,上下文狀態是空的,直到它填充為止。不確定在這種情況下最佳做法是什麼。我應該在哪個生命週期中將初始數據保存到 Context 以便像傳遞道具一樣立即擁有它們?


參考解法

方法 1:

you cannot use ContextAPI in Next.js server‑side (SSR), because it's against hooks rules. https://reactjs.org/warnings/invalid‑hook‑call‑warning.html

React will run getInitialProps first, so the best solution is to fetch data in there and passing it through your Component using ContextAPI.

Let's go ahead and see it working as below:

Create your AppProvider component

Implement your context provider functions you want to pass through your React components.

For this case, we'll create our global Context provider wrapping the entire application in it.

const AppProvider = ({ children }) => {
  const [galleryData, setGalleryData] = React.useState([]);

  const handleGalleryData = galleryData => {
    setGalleryData(galleryData);
  }

  const contextProps = {
    galleryData,
    handleGalleryData
  };

  return (
    <AppContext.Provider value={contextProps}>
      {children}
    </AppContext.Provider>
  );
}

Then wrap your app with this new provider.

<AppProvider>
  <App />
</AppProvider>

And into your pages, such as index.js, try this way:

Index.getInitialProps = async (props) => {
  const { req, res, query, ...others } = props;

  // use your env variables, endpoint URIs
  // ..

  ... fetch whatever you want..
  const galleryProps = await fetch(endpoint); // isomorphic‑unfetch

  return {
    galleryProps,
    query,
    ...others
  };
}

Depending on your Next.js version, you might use getServerSideProps instead of getInitialProps, but be aware of calling it by each request.

Next.js will pre‑render this page on each request using the data returned by getServerSideProps Data Fetching docs

Start using ContextAPI over your components

Then in your components, you can check for this data and store it into ContextAPI

const Index = props => {
  const { galleryProps, query, ...others } = props;
  const [galleryData, setGalleryData] = useState(galleryProps);
  const { handleGalleryData, ...contextRest } = useContext(AppContext);
  ...

  // Here you're going to store data into ContextAPI appropriatly.
  useEffect(() => {
    if (typeof galleryProps === 'object' && _.keys(galleryProps).length > 0) {
      handleGalleryData(galleryProps);
    }
  }, [handleGalleryData]);

  // Other times your page is loaded, you will GET this data from ContextAPI, instead of SSR props.
  useEffect(() => {
    if (_.keys(galleryDataProps).length <= 0 && _.keys(contextRest.galleryData).length > 0) {
      setGalleryData(contextRest.galleryData);
    }
  }, []);

....

return (
  <div>
    {JSON.stringify(galleryData)}
  </div>
);

The use case above isn't the best one, but it brings an understanding of how things work with ContextAPI in Next.js applications. I'll explain it below:

  • The first useEffect() is verifying if the component received a data object from props storing it over ContextAPI.

  • The second one checks if the store got some data

You may fetch data in SSR mode over getInitialProps before your component loads.

References

方法 2:

When you talk about getInitialProps you are talking about Server Sider Render (SSR). If you don't need to do SSR, the example in Next with Context API is enough, otherwise you can use the pageProps in the file _app.js to initialize your Context, read more about Custom App in documentation Custom App

Note: If you're using Next.js 9.3 or newer, we recommend that you use getStaticProps or getServerSideProps instead of getInitialProps.

import { AppProvider } from '../contexts/AppProvider';

function MyApp({ Component, pageProps }) {
  return (
    <AppProvider initialData={pageProps?.initialData}>
      <Component {...pageProps} />
    </AppProvider>
  );
}

export default MyApp;

Then you can initialize your Context with the data obtained by the server.

import { useState, createContext, useMemo } from 'react';

export const AppContext = createContext();

export const AppProvider = ({ children, initialData }) => {
  const [data, setData] = useState(initialData);

  const value = useMemo(() => ({ data, setData }), [data]);

  return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};

But wait!!, how can we get the data from the server of the Page? This can be a bit confusing, but pageProps obtained from the Page with getServerSideProps are always passed through MyApp and then to the child component.

getServerSideProps (in Page) ===> MyApp({ Component, pageProps }) ===> Page({pageProps})

And this is what Page would look like consuming the context. The first time, the Server renders the page and initializes the Context, and then you can get the data or update the context again.


import { useContext } from 'react';
import { AppContext } from '../contexts/AppProvider';

export default function Index() {
  const { data, setData } = useContext(AppContext);

  const handleOnClick = () => {
    setData(`Data from client: ${Date.now()}`);
  };

  console.log(data);

  return (
    <div>
      <div>{JSON.stringify(data)}</div>
      <button onClick={handleOnClick}>Update Context</button>
    </div>
  );
}

export function getServerSideProps() {
  const data = `Data from server: ${Date.now()}`;

  return {
    props: {
      initialData: data,
    },
  };
}

You can check that the console.log(data); is displayed on the server and client console, but then only on the client.

You can view the example online here

(by Goran JakovljevicFrancis RodriguesAlejandro Martínez)

參考文件

  1. Nextjs and Context API (CC BY‑SA 2.5/3.0/4.0)

#react-context #reactjs #Next.js






相關問題

在項目中使用 Context API 我以前會在異步操作創建者中使用 Redux? (Using Context API in a project I would have previously used Redux in - async action creators?)

Nextjs 和上下文 API (Nextjs and Context API)

從 redux-saga 函數訪問 React 上下文值 (Access React context value from redux-saga function)

如何在 Django 應用程序中用 react ContextAPI 替換 Redux (How can you replace Redux by the react ContextAPI in a Django App)

從深度嵌套的組件更新狀態而不重新渲染父組件 (Update state from deeply nested component without re-rendering parents)

更新 useContext 中的值時,組件不會重新呈現 (Component not re rendering when value from useContext is updated)

為什麼我無法從反應上下文訪問變量?你能找出錯誤嗎? (Why am I not able to access variable from react context? Can you identify the mistake?)

React Hook useEffect 缺少依賴項(在上下文中定義的函數) (React Hook useEffect has a missing dependency (function defined in context))

React Context 是道具鑽探的解毒劑嗎? (Is React Context an antidote for prop drilling?)

主題提供者問題 (Theme Provider issue)

打字稿反應上下文+類型'{}'沒有調用簽名 (Typescript react context + Type '{}' has no call signatures)

next.js socket.io 使用效果不更新套接字消息 (next.js socket.io useeffect not updating on socket message)







留言討論