0

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} />;

Gnusson
  • 305
  • 1
  • 2
  • 9
  • Why not just call `return `? Wouldn't that solve the re-rendering issue? The re-render happens because `getLayout` gets redeclared on every render. – juliomalves Oct 14 '21 at 17:44
  • @juliomalves For some reason, this also re-renders the navigation. I think you need to pass the getLayout from every page to the authenticatedRoute function. But I cannot figure out how to do that... – Gnusson Oct 15 '21 at 08:53

0 Answers0