11

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} />
}
mpc75
  • 937
  • 9
  • 22

6 Answers6

9

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;

Mr Washington
  • 1,295
  • 14
  • 15
  • 1
    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? – Richard Trembecký Dec 02 '21 at 13:41
  • 2
    For anyone encountering this, this works! I have a multipage Formik form that only a subdirectory of my app is concerned with. The 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.` – BrandonLenz Jul 01 '22 at 01:31
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.

Dejan Sandic
  • 451
  • 2
  • 10
  • 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. – mpc75 Nov 10 '20 at 13:47
1

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.

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

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

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. :)

Luis Moreno
  • 615
  • 6
  • 6