DEV Community 👩‍💻👨‍💻

Truong Hoang Dung
Truong Hoang Dung

Posted on

A React Hook for universal routing

I'm using @reach/router and love its simplicity.
But i love to use React Hook to get the page params and query string, so i make a simple hook for it.

Firstly, we need a pageContext for page component.

pageContext.ts:

import React, { createContext } from 'react';

const pageContext = createContext({} as any);

export const withPageContext = (Component: any) => (props: any) => {
  return (
    <pageContext.Provider value={{...props}}>
      <Component {...props} />
    </pageContext.Provider>
  );
};

export default pageContext;

This also includes a HOC to inject props into pageComponent to be consumed with useContext hook later.

Second, we need to wrap this pageContext.Provider to all Route component:
Route.tsx:

import React, { 
  FunctionComponent 
} from 'react';
import { 
  RouteComponentProps 
} from '@reach/router';
import {
  LoadableComponent
} from '@loadable/component';
import { withPageContext } from './pageContext';

type Props = { component: FunctionComponent | LoadableComponent<{}> } & RouteComponentProps;

const Route: FunctionComponent<Props> = ({ component, ...rest }) => {
  const Component = withPageContext(component);
  return (
    <Component {...rest} />
  );
};

export default Route;

And lastly, the thing we need, a useRouter hook:
useRouter.ts

import pageContext from './pageContext';
import { useContext } from 'react';
import { parse, parseUrl } from 'query-string';
const isServer = typeof(window) === 'undefined';

const useRouter = () => {
  const { location, ...rest } = useContext(pageContext);
  const queryParams = isServer ?  
    parseUrl(location.pathname).query : 
    parse(location.search);

  const query = {
    ...queryParams,
    ...rest,
  };

  return { query };
};

export default useRouter;

Here, i mapped all props and query string into a query property returned by useRouter and we've done !

Usage:

Firstly, declare your routes with our new Route component:

App.tsx:

import React from 'react';
import Route from './Route';
import { 
  Router 
} from '@reach/router';
import loadable from '@loadable/component';

const User = loadable(() => import('components/User'));

const App = () => {
  return (
    <Router>      
      <Route component={User} path="/users/:userId" />
    </Router>
  );
};

export default App;

Secondly, use useRouter in our User component:

User.tsx:

import React from 'react';

const User = () => {
  const { query } = useRouter();
  const { userId } = query;
  return <div>You requested {userId}</div>;
};

Inspiration:

This is inspired by react-router and nextjs.

Top comments (0)

Join us at DEV
Yes, this is technically an “ad”, but really we just want to ask if you want to join DEV. We have 900k+ developers reading, posting, and enjoying community, and would love to have you.   Create an account and continue your coding journey.