FRMR
FRMR

Reputation: 358

Access React context API from _app in NextJS

I need to access the context API in my _app.js file in order to set global state triggered by the router events. The reason for this is to set a loading state which can be accessed by individual components throughout the app. The problem is is the context is provided from the _app.js file, so I don't have the context's context as it were.

context.js

import React, { createContext, useState } from "react";

export const Context = createContext();

const ContextProvider = (props) => {
    const [isLoading, setIsLoading] = useState(false);

    return (
        <Context.Provider
            value={{
                isLoading,
                setIsLoading,
            }}
        >
            {props.children}
        </Context.Provider>
    );
};

export default ContextProvider;

_app.js

import React, { useContext } from "react";
import App from "next/app";
import Head from "next/head";
import Aux from "../hoc/Aux";
import ContextProvider, { Context } from "../context/context";
import { withRouter } from "next/router";

class MyApp extends App {
    static contextType = Context;

    componentDidMount() {
        this.props.router.events.on("routeChangeStart", () => {
            this.context.isLoading(true);
        });
        this.props.router.events.on("routeChangeComplete", () => {
            this.context.isLoading(false);
        });
        this.props.router.events.on("routeChangeError", () => {
            this.context.isLoading(false);
        });
    }

    render() {
        const { Component, pageProps } = this.props;

        return (
            <Aux>
                <Head>
                    <title>My App</title>
                </Head>
                <ContextProvider>
                    <Component {...pageProps} />
                </ContextProvider>
            </Aux>
        );
    }
}

export default withRouter(MyApp);

Clearly this wouldn't work since _app.js is not wrapped in the context provider. I've tried moving the router event listeners further down the component tree, but then I don't get the loading state from my home page to my dynamically created pages that way.

Is there any workaround that lets me consume context in _app.js? I can't think of any other way I can access loading state globally to conditionally load specific components.

Upvotes: 2

Views: 7911

Answers (2)

ericgio
ericgio

Reputation: 3509

It's not clear to me why you need the context provider to be a parent of _app.js (or a separate component at all). Wouldn't the following work?

class MyApp extends App {
  state = {
    isLoading: false,
  };

  componentDidMount() {
    this.props.router.events.on("routeChangeStart", () => {
      this.setIsLoading(true);
    });

    this.props.router.events.on("routeChangeComplete", () => {
      this.setIsLoading(false);
    });

    this.props.router.events.on("routeChangeError", () => {
      this.setIsLoading(false);
    });
  }

  render() {
    const { Component, pageProps } = this.props;

    return (
      <Aux>
        <Head>
          <title>My App</title>
        </Head>
        <Context.Provider
          value={{
            isLoading: this.state.isLoading,
            setIsLoading: this.setIsLoading,
          }}>
          <Component {...pageProps} />
        </Context.Provider>
      </Aux>
    );
  }

  setIsLoading = (isLoading) => {
    this.setState({ isLoading });
  }
}

export default withRouter(MyApp);

Alternatively (if there's something I'm really not understanding about your use case), you could create a HoC:

function withContext(Component) {
  return (props) => (
    <ContextProvider>
      <Component {...props} />
    </ContextProvider>
  );
}

class MyApp extends App {
  ...
}

export default withContext(withRouter(MyApp));

Upvotes: 3

leerob
leerob

Reputation: 3122

You can show a loading indicator using nprogress. For example:

import NProgress from "nprogress";
import Router from "next/router";

Router.onRouteChangeStart = () => NProgress.start();
Router.onRouteChangeComplete = () => NProgress.done();
Router.onRouteChangeError = () => NProgress.done();

Source

Upvotes: 1

Related Questions