Building a Simple Routing Solution for React

Building a Simple Routing Solution for React

This article guides you through crafting a bespoke routing solution for React applications, prioritizing simplicity and maintainability. We'll be leveraging TypeScript for enhanced type safety and code clarity, aiming to replace React Router in smaller applications without the complexity of a heavyweight solution.

Step 1. Setup Navigation Context and Provider

The core of our routing system lies within the `navigation.tsx` file. Here, we define the NavigationContext which encapsulates the current path (`currentPath`) and navigation function (`navigate`).

navigation.tsx
import React, { createContext, useEffect, useState } from "react";

type NavigationContextType = {
  currentPath: string;
  navigate: (to: string) => void;
};

const NavigationContext = createContext<NavigationContextType>(
  {} as NavigationContextType
);

const NavigationProvider = ({ children }: { children: React.ReactNode }) => {
  const [currentPath, setCurrentPath] = useState(window.location.pathname);

  useEffect(() => {
    const handler = () => {
      setCurrentPath(window.location.pathname);
    };

    window.addEventListener("popstate", handler);

    // clean up
    return () => {
      window.removeEventListener("popstate", handler);
    };
  }, []);

  const navigate = (to: string) => {
    window.history.pushState({}, "", to);
    return setCurrentPath(to);
  };

  return (
    <NavigationContext.Provider value={{ currentPath, navigate }}>
      {children}
    </NavigationContext.Provider>
  );
};

export { NavigationProvider };
export default NavigationContext;        

The `NavigationProvider` component handles updating the `currentPath` based on browser history changes. The `useEffect` hook ensures that the currentPath is synchronized with the browser's history, effectively enabling navigation without full page reloads.

The navigate function uses window.history.pushState to manipulate the browser's history stack, allowing seamless transitions between routes.

We will use `NavigationProvider` within `main.tsx` to make sure our application has access to the navigation system we build.

main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import { NavigationProvider } from "./context/navigation.tsx";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <NavigationProvider>
      <App />
    </NavigationProvider>
  </StrictMode>
);
        

Step 2. Abstract Context with `useNavigation` Hook

To abstract the complexities of accessing the `NavigationContext`, we introduce the `useNavigation` hook.

useNavigation.tsx
import { useContext } from "react";
import NavigationContext from "../context/navigation";

const useNavigation = () => {
  return useContext(NavigationContext);
};

export default useNavigation;        

This hook simply extracts the currentPath and navigate function from the NavigationContext through useContext, providing a convenient way to interact with the navigation state within our components without having to repetitively import `NavigationContext` and `useContext` when needing to access the `currentPath` variable or `navigate` function.

Step 3. Create the `Route` Component

The `Route` component serves as the building block for defining our application routes.

Route.tsx
import React from "react";
import useNavigation from "../hooks/useNavigation";

interface RouteProps {
  path: string;
  children: React.ReactNode;
}

const Route = ({ path, children }: RouteProps) => {
  const { currentPath } = useNavigation();

  if (path === currentPath) {
    return children;
  }

  return null;
};

export default Route;        

This component, utilizing the `useNavigation` hook, conditionally renders its children based on the current path. If the path property matches the `currentPath`, the children are displayed; otherwise, nothing is rendered.

Step 4. Map Application Routes

I mapped my routes in `App.tsx` to provide the structure of the application routes.

App.tsx
import Route from "./components/Route";
import Link from "./components/Link";

function App() {
  return (
    <>
        <Route path="/">
          <h1>Hello, World! I am the root route.</h1>
        </Route>
        <Route path="/lorem">
          <p>Lorem ipsum...</p>
        </Route>
    </>
  );
}

export default App;        

Here, we use the Route component to define two routes: one for the root path (/) and another for a /lorem path. Each route is responsible for rendering the appropriate content when navigated to.

Step 5. Enable Navigation with `Link` Component

The `Link` component will be responsible for facilitating navigation within our application.

Link.tsx
import React, { PropsWithChildren } from "react";
import useNavigation from "../hooks/useNavigation";

interface LinkProps extends React.HTMLAttributes<HTMLAnchorElement> {
  to: string;
  className?: string;
}

const Link = ({ to, children, className }: PropsWithChildren<LinkProps>) => {
  const { navigate } = useNavigation();

  const handleClick = (
    event: React.MouseEvent<HTMLAnchorElement, MouseEvent>
  ) => {
    if (event.metaKey || event.ctrlKey) {
      return;
    }
    event.preventDefault();
    navigate(to);
  };

  return (
    <a onClick={handleClick} href={to}>
      {children}
    </a>
  );
};

export default Link;        

The Link component utilizes the `navigate` function from the `useNavigation` hook to update the `currentPath` when clicked. It also prevents the default anchor tag behavior, ensuring that navigation doesn't trigger a full page reload, while allowing users to open links in new tabs using Ctrl+click or Cmd+click.

Wrapping Up

By integrating these components within your React application, you'll be able to create a simple yet effective routing solution without the overhead of a full-fledged library. This approach emphasizes clean code, maintainability, and control over your application's routing logic, allowing you to customize the navigation experience to perfectly fit your needs.

Want to see this code in action? Check out my React component-library repo where I implement this solution.


要查看或添加评论,请登录

社区洞察

其他会员也浏览了