How do I only wrap some pages in a Context Provider with Next.js?
Asked Answered
M

7

12

I have three pages that I want to share data between (these are the core of the web app) but I also have a bunch of blog pages that don't care about that data. Everywhere I've looked suggests putting the Provider in the _app.tsx file. If I understand that correctly if I wrapp MyApp with the provider, if someone goes straight to www.myurl.com/blog/my-blog-1 (from google), that will cause the provider to run its functions; even if that page won't call useContext

How do I only wrap three pages in the Provider and leave out the blog pages?

For example:

Here's what my _app.tsx looks like:

import { AppProps } from 'next/app'
import '../styles/index.css'

export default function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />
}
Munster answered 6/11, 2020 at 21:24 Comment(0)
C
11

A bit late to the party but there is actually an official way to do this: Use a per page layout. First slightly tweak your _app.tsx file:

import type { ReactElement, ReactNode } from 'react';
import { AppProps } from 'next/app';
import type { NextPage } from 'next';

type NextPageWithLayout = NextPage & {
    getLayout?: (page: ReactElement) => ReactNode;
};

type AppPropsWithLayout = AppProps & {
    Component: NextPageWithLayout;
};
export const App = ({ Component, pageProps }: AppPropsWithLayout): unknown => {

    // Use the custom layout defined at the page level, if available
    const getLayout = Component.getLayout ?? ((page) => page);

    return getLayout(<Component {...pageProps} />);
};

Then in a Page component write the following:

// DashboardPage.ts that need to have a UserProvider and CompanyProvider
import Layout from '@components/Layout'; // Default Layout that is imported for all the page
import { ReactElement } from 'react';
import { UserProvider } from '@contexts/user';
import { CompanyProvider } from '@contexts/company';

const DashboardPage = (): JSX.Element => {
    return <DashboardContainer />;
};

// Custom Layout to wrap the page within the User and company providers
DashboardPage.getLayout = function getLayout(page: ReactElement) {
    return (
        <Layout title="Dashboard page">
            <UserProvider>
                <CompanyProvider>{page}</CompanyProvider>
            </UserProvider>
        </Layout>
    );
};

export default DashboardPage;

Cardiovascular answered 13/9, 2021 at 17:6 Comment(2)
Hi, does this really preserve context values between pages? E.g. when you logged in, you have user in your UserProvider and you navigate to other page that's using the same layout, I doubt the user will stay in the context, will it?Selfseeker
For anyone encountering this, this works! I have a multipage Formik form that only a subdirectory of my app is concerned with. The <Formik> tag which manages the Context is in the wrapping component that my layout uses. I confirmed that when I navigate between pages using this layout, the context is preserved. As the docs say: This layout pattern enables state persistence because the React component tree is maintained between page transitions. With the component tree, React can understand which elements have changed to preserve state.Piranha
M
3

Well, this is an interesting issue.

Challenge is in the way Next implements file-based routing. Since there is no way to create a wrapper for the group of pages only out-of-the-box thing you can do is to wrap the App in the context providers. But that doesn't really resolve your issue.

SOOO... I think there is a workaround. If you want to be able to wrap a certain group of pages in the context provider, first, you need to replace the file-based router with the react-router.

There is a very interesting article on this topic by Andrea Carraro. I think you should try this out: https://dev.to/toomuchdesign/next-js-react-router-2kl8

I will try to find another solution as well, but let me know did this worked for you.

Magniloquent answered 9/11, 2020 at 22:41 Comment(1)
Thanks for the suggestion and for helping me think about this. I was hoping to keep the file-based router since it's one of the things I really like about Next.js. I ended up just adding the minimal amount of auth info in context and not sharing as much as I initially intended.Munster
D
2

There is workaround to wrap components of a specific path with a wrapper without react-router. Check out this video: https://www.youtube.com/watch?v=69-mnojSa0M

The video shows you how to wrap components of a sub-path with a nested layout. Create a nested layout and wrap that that layout with your Context Provider.

Deena answered 27/8, 2021 at 17:30 Comment(0)
A
0

I'm kind new to Next.js and looking for the same question. Maybe we can just use pathname and query from the useRouter hook and try some conditionals in our Contexts or in the custom _app

next/router

Ancel answered 11/4, 2021 at 3:18 Comment(0)
A
0

Very late to answer my question, but my approach might help someone in the future. I used a page as a wrapper, and to navigate around I used query params.


For example

Aalesund answered 27/1, 2022 at 11:2 Comment(0)
C
0

I use contextProvider and check the path pages in _app.js

myContextProvider.js

import React, { useState } from 'react';
const Context = React.createContext({});

export function myContextProvider({ children }) {
     const [ state1, setState1 ] = useState(true);

     return <Context.Provider value={{ state1, setState1 }}>
          {children} 
     </Context.Provider>;
}

export default Context;

_app.js

import { useRouter } from 'youreFavoRouter';
import { myContextProvider } from './myContextProvider.js';

export default function MyApp({ Component, pageProps }) {
    const router = useRouter();
    
    const isBlogPage = router.pathname.includes('blog');

    return (
        <div>
            {isBlogPage ? (
                <Component {...pageProps} />
            ) : (
                <myContextProvider>
                    <Component {...pageProps} />
                </myContextProvider>
            )}
        </div>
    );
}

Now all states and function on myContextProvider can see and use on pages than you choose. I hope help anyone. :)

Churlish answered 3/10, 2022 at 17:43 Comment(0)
K
0

Hey I thought I should post this somewhat of a solution I found that works for me so far. I thought maybe I could import my context in to one of my pages and test if wrapping a provider around with this would work. I am also new to Nextjs and don't know if this is a sound way of doing so, but it seems to work for me. I can access all my provided stuff from this component userprofile thats within my [username].tsx page.

Disclaimer Ive only done this for testing purposes

   // pages/[username].tsx
   import { UserProfile } from '@component/app/userprofile';


   const UserPage = () => {
   const router = useRouter();
   const { username } = router.query;

   useEffect(() => {
    
   console.log('Username:', username);
    }, [username]);

   return (
     <SampleContextProviderComponent>
       {user && user.map((user, index) => {
          if(username === user.username){

          return <UserProfile key={index} user={user}/>
          }
        })}
  
     </SampleContextProviderComponent>
     );
    };

    export default UserPage;
Keos answered 21/3 at 8:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.