Reputation: 358
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
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
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();
Upvotes: 1