Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nextjs and Context API

Working with Next.js and I am trying to save data inside Context API state after fetching them within getInitialProps, to fix props drilling.

But since getInitialProps is a static method, we can't access it via this.context. I managed to save them within componentDidMount, but in that case, the Context state is empty on the first-page load until it populates. Not sure what the best practice would be in this case. In which lifecycle should I save initial data to Context in order to have them right away like with props passing?

like image 948
Goran Jakovljevic Avatar asked Jan 10 '19 11:01

Goran Jakovljevic


People also ask

Can we use context API in NextJS?

That being said, we can easily implement Context API using React Hooks into NextJS. Let's step through the creation of a simple custom Auth to allow our NextJS app to see the logged in status of a user, and the users details throughout every component and page.

What is context NextJS?

js Context and React Context. As Next. js is a framework built on top of React, we're able to leverage the latest features released by the team over at Facebook.

Is there any reason not to use NextJS?

Disadvantages of NextJS Although NextJS is developing rapidly and many features arrive, it still has some cons and issues which you can see below: Cost of flexibility – Next JS does not provide many built-in front pages, so you have to create the whole front-end layer from the ground up.


2 Answers

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

  • reactjs.org - Context.Provider & useContext() hook
  • Next.js docs - getInitialProps
  • github.com - example of Next.js with ContextAPI
like image 144
Francis Rodrigues Avatar answered Sep 17 '22 13:09

Francis Rodrigues


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

like image 33
Alejandro Martínez Avatar answered Sep 19 '22 13:09

Alejandro Martínez