loading...

A React Hook for universal routing

revskill10 profile image Truong Hoang Dung ・2 min read

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.

Discussion

pic
Editor guide