Background to the problem
HOCs
When dealing with Next.Js pages a common practice is to use HOC
(Higher-Order Component) to avoid retyping the base of a page.
For example, an authentication HOC can be used to check if a user is authenticated or not. Depending on the outcome the user can either access the page or be redirected to a sign-in page.
Layouts
Another practice that is commonly used by Next.Js programmers is Persistent Layouts
. The Persistent layout is an "area" on the page that will not re-render when the user is navigating through pages. This is great for UX (User Experience), for example, the scroll-position of a menu remains on page switch.
Some good links
NextJs.org - Persistent Layout Documentation
CheatCode.co - How to Handle Authenticated Routes (The HOC used below)
The problem
When combining these two practices a problem with persistence occurs. (Read comments inside AuthenticationRoute.js
This is a very simple index.js
import authenticatedRoute from '@components/auth/AuthenticatedRoute';
const App = () => {
return (
<section>
<h1>Logged In</h1>
<h1>App</h1>
</section>
);
};
export default authenticatedRoute(App);
The layout Layout.js
import Link from 'next/link';
import Navbar from '@components/layouts/Navbar';
import SideMenu from '@components/layouts/SideMenu';
function Layout({ children }) {
const baseUrl = '/app';
return (
<section>
<Navbar>
<li>
<Link href={`${baseUrl}/`}>Home</Link>
</li>
<li>
<Link href={`${baseUrl}/test1`}>Test 1</Link>
</li>
<li>
<Link href={`${baseUrl}/test2`}>Test 2</Link>
</li>
</Navbar>
<main>{children}</main>
</section>;
);
}
export default Layout;
And lastly the HOC AuthenticationRoute.js
import { Component as component } from 'react';
import Router from 'next/router';
import Layout from '@components/layouts/app/Layout';
const authenticatedRoute = (Component = null, options = {}) => {
class AuthenticatedRoute extends component {
state = {
loading: true,
};
componentDidMount() {
const isSignedIn = true;
if (isSignedIn) {
this.setState({ loading: false });
} else {
Router.push(options.pathAfterFailure || '/sign_in');
}
}
render() {
const { loading } = this.state;
if (loading) {
return <div />;
}
// This will return the page without the layout
return <Component {...this.props} />;
// Removing the line above and using this instead
// the page now renders with the layout. BUT...
// The layout is not persistent, it will re-render
// everytime a user navigate.
const getLayout = (page) => <Layout>{page}</Layout>;
return getLayout(<Component {...this.props} />);
}
}
return AuthenticatedRoute;
};
export default authenticatedRoute;
Without HOC
Inside index.js
this is working when not calling authenticatedRoute:
App.getLayout = getLayout;
export default App;
So my guess is that the authenticatedRoute should return something else then return <Component {...this.props} />;