11

I want to get values of history and match in this.props. I am using next/router to import withRouter. I wanted to use this.props.match.params to get values in the url and history.push to redirect. How can get this in Next.js. Right now I am not getting history and match in this.props.

juliomalves
  • 42,130
  • 20
  • 150
  • 146
Logeswari
  • 387
  • 3
  • 5
  • 9

5 Answers5

11

EDIT: I had to turn back to the global Router. The useRouter hook does not work, since its life cycle seems to be different and thus I cannot grab the exact values that I need at the given times.

.....
    if (
      !shallow &&
      !(EXCEPTIONS.includes(nextUrl) || EXCEPTIONS.includes(Router.asPath)) &&
      nextUrl !== Router.asPath
    ) {
      setPreviousRoute(Router.asPath);
    }
  };

  useEffect(() => {
    Router.events.on('beforeHistoryChange', handleBeforeHistoryChange);

    return () => {
      Router.events.off('beforeHistoryChange', handleBeforeHistoryChange);
    };
  }, []);

  return { previousRoute };
};

EDIT: Since the global Router object pollution is not a nice thing to do, you could use a state variable instead:

const EXCEPTIONS = ['/sign-up'];

/**
 * Saves the current URL before changing the route.
 */
const useRouteUrlHistory = () => {
  const [previousRoute, setPreviousRouter] = useState('');
  const router = useRouter();

  const handleBeforeHistoryChange = (url) => {
    const [nextUrl] = url?.split('?') || [];

    if (
      !(EXCEPTIONS.includes(nextUrl) || EXCEPTIONS.includes(router.asPath)) &&
      nextUrl !== router.asPath
    ) {
      setPreviousRouter(router.asPath);
    }
  };

  useEffect(() => {
    router.events.on('beforeHistoryChange', handleBeforeHistoryChange);

    return () => {
      router.events.off('beforeHistoryChange', handleBeforeHistoryChange);
    };
  }, []);

  return { previousRoute };
};

export default useRouteUrlHistory;

You have to use the hook in your _app.js since you listen for route changes and want to save/access the previousRoute globally.

const MyApp = ({ Component, pageProps }) => {

  const { previousRoute } = useRouteUrlHistory();

  return (
        <ApplicationContext.Provider
          value={{
            previousRoute,
            ... another global context
          }}
        >
           ... components etc.
        </ApplicationContext.Provider>

=====

I have not found an elegant way to be able to access Router.history in its entirety. But often enough I wanted to access at least the last visited route, which forced me to this workaround. It might not be the most elegant way since it pollutes the global Router object, but as I said I have not found any alternative.

I only check for the actual route which was visited previously. You may also implement your logic to check the params. The hook plugs-in before the Router handles the actual Route change. Which allows you to listen to the Router.previousRoute property within you application.

KEEP IN MIND if you do shallow Routing you may not affect the Router.history, which will probably not trigger this event hook.

const EXCEPTIONS = ['/sign-up'];

/**
 * Saves the current URL before changing the route.
 * The previousRoute is then accessible via the global Router.
 */
const useRouteUrlHistory = () => {
  const handleBeforeHistoryChange = (url) => {
    const [nextUrl] = url?.split('?') || [];

    if (
      !(EXCEPTIONS.includes(nextUrl) || EXCEPTIONS.includes(Router.asPath)) &&
      nextUrl !== Router.asPath
    ) {
      Router.previousRoute = Router.asPath;
    }
  };

  useEffect(() => {
    Router.events.on('beforeHistoryChange', handleBeforeHistoryChange);

    return () => {
      Router.events.off('beforeHistoryChange', handleBeforeHistoryChange);
    };
  }, []);
};

export default useRouteUrlHistory;
dimisus
  • 392
  • 4
  • 11
  • Where do you use this hook, surely it's required to be near the app root and also in the child that wants access to the routing history? – Dawson B Apr 11 '21 at 21:59
  • Does not matter, in any functional react component. The main issue here is that I pollute the Router object and this does not work that good with the NextJs useRouter hook. Since I use a custom router from next-i18next this is not particularly an issue here since the Router object is kind of globally accessible. – dimisus Apr 12 '21 at 13:47
  • 1
    I have edited the example to use a hook in _app.js without Router pollution – dimisus Apr 12 '21 at 13:54
  • 1
    man it's frustrating thatthere isn't a built in way to do this – Patrick Michaelsen Mar 14 '23 at 04:17
5

next/router doesn't provide you history as react-dom-router does, it gives you something called query in your component's props.

This is the official usage example.

import { useRouter } from 'next/router'

const Post = () => {
  const router = useRouter()
  const { pid } = router.query

  return <p>Post: {pid}</p>
}

export default Post

If you want to push routes, you can follow this one. example.

import Router from 'next/router'

function ReadMore() {
  return (
    <div>
      Click <span onClick={() => Router.push('/about')}>here</span> to read more
    </div>
  )
}

export default ReadMore
Sultan H.
  • 2,908
  • 2
  • 11
  • 23
5

next/router doesn't provide history, but we can use next router.events to get previous path and current path at global level.

Reference

_app.js

import {useEffect} from "react"
import { useRouter } from 'next/router'


let prevRoute
let currentRoute
const MyApp = ({ Component, pageProps }) => {
    const router = useRouter()
    currentRoute = router.pathname
    
    // THAT'S THE MAIN WORKAROUND
    
    useEffect(() => {
        const handleRouteChange = (url, { shallow }) => {
            prevRoute = currentRoute
            currentRoute = url
            console.log(prevRoute, currentRoute)
        }
        router.events.on('routeChangeStart', handleRouteChange)

        // If the component is unmounted, unsubscribe
        // from the event with the `off` method:
        return () => {
            router.events.off('routeChangeStart', handleRouteChange)
        }
    }, [])
    
    
    
    
    return (
        <>
            <Component prevRoute={prevRoute} currentRoute={currentRoute} {...pageProps} />
        </>
    );
};
export default MyApp;
Anurag Tripathi
  • 784
  • 1
  • 13
  • 13
2

You can as well use Router

import Router from 'next/router'
// on Submit handler
Router.push(url)

where url is the page you want to redirect to.

Router API

0

The solution here tracks all of your router history:

https://github.com/vercel/next.js/discussions/16337

I implemented it like so:

// useHistory.ts
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';

// https://github.com/vercel/next.js/discussions/16337
export function useHistory() {
    const router = useRouter();
    const [history, setHistory] = useState<string[]>([]);

    useEffect(() => {
        const handleRouteChange = (url: any, { shallow }: any) => {
            if (!shallow) {
                setHistory(prevState => [...prevState, url]);
            }
        };

        router.beforePopState(() => {
            setHistory(prevState => prevState.slice(0, -2));
            return true;
        });

        router.events.on('routeChangeStart', handleRouteChange);

        return () => {
            router.events.off('routeChangeStart', handleRouteChange);
        };
    });

    return { history, canGoBack: () => history.length > 1 };
}
// Context.ts

import React from 'react';

export interface ContextProps {
  history: string[],
}

export const Context = React.createContext<ContextProps>({} as any);

export function withContext<T>(
  Component: React.FunctionComponent<T & ContextProps>
): React.FunctionComponent<T> {
  const WithContext = (props: T) => (
    <Context.Consumer>
      {context => {
        return (
          <Component 
            {...props}
            {...context}
          />
        );
      }}
    </Context.Consumer>
  );
  return WithContext;
}
// pages/_app.tsx
import '../styles.scss';
import Head from "next/head";
import { Context } from '../components/Context';
import { useHistory } from '../hooks/useHistory';

interface WebProps<T>{
  Component: React.FunctionComponent,
  pageProps: T,
}

// consider using tailwind: https://tailwindcss.com/docs/guides/nextjs
export default function Web({ Component, pageProps }: WebProps<any>) {
  const { history } = useHistory();
  console.log(history);
  const context = {
    history,
  }
  return (
    <>
      <Head>
          <title>pagemark</title>
      </Head>
      <Context.Provider value={context}>
        <Component {...pageProps} />
      </Context.Provider>
    </>
  );
}