How to reload a Next.js page's initial props without reloading the whole page
RobbieGM
Posted on May 18, 2020
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!
Posted on May 18, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.