DEV Community

RobbieGM
RobbieGM

Posted on

How to reload a Next.js page's initial props without reloading the whole page

If you have made a Next.js page that loads data in its getInitialProps function, you may have wanted to reload it in order to fetch the newest data after making some mutating API call. Hard-reloading the page will reset the user's scroll position and take extra time, so clearly that is not the best solution. You could also make some fetchData function that gets called in getInitialProps, passes its data into the default value of a useState hook, and then gets updated by calling fetchData again followed by setting the state. But wouldn't it be convenient if you could just have a function that reloaded your initial props?
I made a higher order component to wrap Next.js pages that does exactly this. Here it is in TypeScript:

import { NextPage, NextPageContext } from 'next';
import React, { useState, PropsWithChildren, ComponentType } from 'react';

export interface SoftReloadProps {
  /**
   * Reloads the page's initialProps without causing a real browser "hard" reload.
   */
  softReload(): void;
}

type OmitSoftReloadProps<T> = Omit<T, keyof SoftReloadProps>;
type SoftReloadablePageInitialProps<TProps> = OmitSoftReloadProps<TProps> & { context: NextPageContext };
export type NextPageWithInitialProps<P, IP = P> = NextPage<P, IP> & Required<Pick<NextPage<P, IP>, 'getInitialProps'>>;

/**
 * Removes never-used context values to reduce bloat. Context values may come from server but then
 * be used client-side because they are saved in initial props.
 */
function minifyContext(context: NextPageContext): NextPageContext {
  return { ...context, req: undefined, res: undefined };
}

const withSoftReload = <TProps extends SoftReloadProps>(
  Page: NextPageWithInitialProps<TProps, OmitSoftReloadProps<TProps>>
): NextPage<SoftReloadablePageInitialProps<TProps>> => {
  async function getInitialProps(ctx: NextPageContext): Promise<SoftReloadablePageInitialProps<TProps>> {
    return { context: minifyContext(ctx), ...(await Page.getInitialProps(ctx)) };
  }
  const omitContextFromProps = ({
    context,
    ...props
  }: SoftReloadablePageInitialProps<TProps>): OmitSoftReloadProps<TProps> => props as any;
  const NewPage: NextPageWithInitialProps<SoftReloadablePageInitialProps<TProps>> = props => {
    // set inner page initial props to wrapper initial props minus context
    const [initialProps, setInitialProps] = useState(omitContextFromProps(props));
    async function softReload() {
      setInitialProps({ children: null, ...(await Page.getInitialProps(props.context)) });
    }
    return (
      <Page
        {...(({ ...initialProps, softReload } as Omit<TProps, keyof SoftReloadProps> & SoftReloadProps) as TProps)}
      />
    );
  };
  NewPage.getInitialProps = getInitialProps;
  NewPage.displayName = `withSoftReload(${Page.displayName})`;
  return NewPage;
};

export default withSoftReload;

You can use this HOC like this in your pages:

interface InitialProps {
  data: string;
}

const MyPage: NextPageWithInitialProps<InitialProps & SoftReloadProps, InitialProps> = ({ data, softReload }) => (
  <div>
    {data}
    <button onClick={softReload}>Refresh</button>
  </div>
);

MyPage.getInitialProps = async (ctx) => {
  // fetch data
};

export default withSoftReload(MyPage);

Hope you enjoy!

Top comments (3)

Collapse
 
w3bdesign profile image
w3bdesign

From the NextJS documentation:

If you're using Next.js 9.3 or newer, we recommend that you use getStaticProps or getServerSideProps instead of getInitialProps.

These new data fetching methods allow you to have a granular choice between static generation and server-side rendering.

Collapse
 
robbiegm profile image
RobbieGM

I've considered using getServerSideProps but then it wouldn't work client side. I use isomorphic fetching in getInitialProps, what is the new recommended way to do that?

Collapse
 
bayuangora profile image
Bayu Angora

How to implement this on Hugo?