Categories
React

React App Routing with Wouter — Active Links, Trailing Slash, and Nested Paths

Wouter is a library that lets us loading React components according to URLs.

In this article, we’ll look at how to add routing to a React app with Wouter.

Active Links

We can set the active link by checking the isActive variable returned from the useRoute prop.

For instance, we can write:

import React from "react";
import { Link, Route, Router, useRoute } from "wouter";

const InboxPage = () => {
  return (
    <div>
      <p>inbox</p>
    </div>
  );
};

const UserPage = () => {
  const [, params] = useRoute("/users/:name");
  return <div>Hello, {params.name}!</div>;
};

const ActiveLink = (props) => {
  const [isActive] = useRoute(props.href);

  return (
    <Link {...props}>
      <a className={isActive ? "active" : ""}>{props.children}</a>
    </Link>
  );
};

export default function App() {
  return (
    <div>
      <style>
        {`.active {
            font-weight: bold
          }`}
      </style>
      <Router>
        <ActiveLink href="/users/foo">Users</ActiveLink>
        <ActiveLink href="/about">About</ActiveLink>
        <Route path="/about">
          <p>About Us</p>
        </Route>
        <Route path="/users/:name" component={UserPage}></Route>
        <Route path="/inbox" component={InboxPage} />
      </Router>
    </div>
  );
}

We have the ActiveLink component that calls the useRoute hook with the current URL and returns an array with the isActive variable.

isActive is true means the link for the current URL is active.

Trailing Slash

We can make Wouter match a route with the trailing slash with a custom URL matcher.

For instance, we can write:

import React from "react";
import { Route, Router } from "wouter";
import makeMatcher from "wouter/matcher";
import { pathToRegexp } from "path-to-regexp";

const customMatcher = makeMatcher((path) => {
  let keys = [];
  const regexp = pathToRegexp(path, keys, { strict: true });
  return { keys, regexp };
});

export default function App() {
  return (
    <div>
      <Router matcher={customMatcher}>
        <Route path="/foo">foo</Route>
        <Route path="/foo/">/foo/</Route>
      </Router>
    </div>
  );
}

We call makeMatcher with a callback to return an object with an object with strict set to true .

This will make sure that URLs with a trailing and without but otherwise are the same are considered different.

Nested Paths

We can add nested paths by setting the base path.

For instance, we can write:

import React from "react";
import { Link, Route, Router, useLocation, useRouter } from "wouter";

const NestedRoutes = (props) => {
  const router = useRouter();
  const [parentLocation] = useLocation();

  const nestedBase = `${router.base}${props.base}`;
  if (!parentLocation.startsWith(nestedBase)) return null;

  return (
    <Router base={nestedBase} key={nestedBase}>
      {props.children}
    </Router>
  );
};

export default function App() {
  return (
    <div>
      <Router>
        <Route path="/about">about</Route>
        <NestedRoutes base="/dashboard">
          <Link to="/users" />
          <Route path="/users">users</Route>
        </NestedRoutes>
      </Router>
    </div>
  );
}

We created the NestedRoutes component that takes the base prop to set the base path.

From it, we created the nestedBase variable by combining router.base and props.base to create the base path.

Then we set the base prop’s value to nestedBase to set the nested routes’ base path.

Conclusion

We can check which link is active, adding trailing slash matches, and nested paths with Wouter.

Categories
React

React App Routing with Wouter — Redirect, Default Route, and Base Path

Wouter is a library that lets us loading React components according to URLs.

In this article, we’ll look at how to add routing to a React app with Wouter.

Redirect

We can redirect to another route component with the setLocation function.

For instance, we can write:

import React from "react";
import { Route, Router, useLocation } from "wouter";

const InboxPage = () => {
  return (
    <div>
      <p>inbox</p>
    </div>
  );
};
export default function App() {
  const [, setLocation] = useLocation();

  return (
    <div>
      <a href="#" onClick={() => setLocation("/about")}>
        About
      </a>
      <Router>
        <Route path="/about">
          <p>About Us</p>
        </Route>
        <Route path="/users/:name">
          {(params) => <div>Hello, {params.name}!</div>}
        </Route>
        <Route path="/inbox" component={InboxPage} />
      </Router>
    </div>
  );
}

We call the useLocation hook to return the setLocation function.

Then we call that in the onClick handler to go to the /about route.

Matching Dynamic Segments

We can use the useRoute hook to match dynamic route segments.

For instance, we write:

import React from "react";
import { Link, Route, Router, useRoute } from "wouter";

const InboxPage = () => {
  return (
    <div>
      <p>inbox</p>
    </div>
  );
};

const UserPage = () => {
  const [, params] = useRoute("/users/:name");
  return <div>Hello, {params.name}!</div>;
};

export default function App() {
  return (
    <div>
      <Router>
        <Link href="/users/foo">Users</Link>
        <Route path="/about">
          <p>About Us</p>
        </Route>
        <Route path="/users/:name" component={UserPage}></Route>
        <Route path="/inbox" component={InboxPage} />
      </Router>
    </div>
  );
}

In the UserPage , we call the useroute hook with the route pattern to get the name parameter from the params oject.

In the Route ‘s path prop, we have the :name placeholder to add the placeholder.

We can also match the /foo?/:bar* pattern to add the wildcard bar placeholder.

/foo/baz? has the optional baz placeholder.

And /foo/bar+ with bar being one or more segments is also supported for matching.

Base Path

We can add a base path with the Router component’s base prop.

For instance, we write:

import React from "react";
import { Link, Route, Router, useRoute } from "wouter";

const InboxPage = () => {
  return (
    <div>
      <p>inbox</p>
    </div>
  );
};

const UserPage = () => {
  const [, params] = useRoute("/users/:name");
  return <div>Hello, {params.name}!</div>;
};

export default function App() {
  return (
    <div>
      <Router base="/app">
        <Link href="/users/foo">Users</Link>
        <Route path="/about">
          <p>About Us</p>
        </Route>
        <Route path="/users/:name" component={UserPage}></Route>
        <Route path="/inbox" component={InboxPage} />
      </Router>
    </div>
  );
}

to set the base path to /app .

Default Route

We can add a default route by adding the Route component without adding the path prop.

For instance, we write:

import React from "react";
import { Route, Switch } from "wouter";

export default function App() {
  return (
    <div>
      <Switch>
        <Route path="/about">
          <p>About Us</p>
        </Route>
        <Route>Not Found</Route>
      </Switch>
    </div>
  );
}

to add the Not Found route.

The default route should always come last so the other patterns can be checked before showing the not found route.

Conclusion

We can add redirects, match dynamic route segments, set the base path, and add a default route with Wouter into our React app.

Categories
React

React App Routing with Wouter — Route Changes, Switches, and Links

Wouter is a library that lets us loading React components according to URLs.

In this article, we’ll look at how to add routing to a React app with Wouter.

Watch Router Changes with useRouter

We can use the useRouter hook to watch for changes to the router object.

For instance, we write:

import React, { useEffect } from "react";
import { Link, Route, Router, useRouter } from "wouter";

const InboxPage = () => {
  return (
    <div>
      <p>inbox</p>
    </div>
  );
};

export default function App() {
  const router = useRouter();

  useEffect(() => {
    console.log(router);
  }, [router]);

  return (
    <div>
      <Link href="/inbox">
        <a>Inbox</a>
      </Link>
      <Router>
        <Route path="/about">
          <p>About Us</p>
        </Route>
        <Route path="/users/:name">
          {(params) => <div>Hello, {params.name}!</div>}
        </Route>
        <Route path="/inbox" component={InboxPage} />
      </Router>
    </div>
  );
}

We use the useEffect hook to watch for router changes.

router has the hook property, which is the useLocation hook by default.

Also, we can add our own properties to the router object to pass data between routes

Route Component

We can use the Route component to define routes.

For instance, we write:

import React, { useEffect } from "react";
import { Link, Route, Router, useRouter } from "wouter";

const InboxPage = () => {
  return (
    <div>
      <p>inbox</p>
    </div>
  );
};

export default function App() {
  return (
    <div>
      <Link href="/inbox">
        <a>Inbox</a>
      </Link>
      <Router>
        <Route path="/about">
          <p>About Us</p>
        </Route>
        <Route path="/users/:name">
          {(params) => <div>Hello, {params.name}!</div>}
        </Route>
        <Route path="/inbox" component={InboxPage} />
      </Router>
    </div>
  );
}

We add the Route component and set the path prop to define the routes.

:name is a URL parameter placeholder.

And we can get its value from the params parameter.

Link Component

The Link component lets us add links to let us navigate to different pages.

For instance, we write:

import React, { useEffect } from "react";
import { Link, Route, Router, useRouter } from "wouter";

const InboxPage = () => {
  return (
    <div>
      <p>inbox</p>
    </div>
  );
};

export default function App() {
  return (
    <div>
      <Link href="/about">
        <a>About</a>
      </Link>
      <Router>
        <Route path="/about">
          <p>About Us</p>
        </Route>
        <Route path="/users/:name">
          {(params) => <div>Hello, {params.name}!</div>}
        </Route>
        <Route path="/inbox" component={InboxPage} />
      </Router>
    </div>
  );
}

to add a link to go to the About page when we click it.

Switch Component

The Switch component lets us add exclusive routing into our React app.

It makes sure that only one route is rendered at a time.

For instance, we write:

import React from "react";
import { Link, Route, Switch } from "wouter";

const AllOrders = () => {
  return (
    <div>
      <p>all orders</p>
    </div>
  );
};

const Orders = ({ params }) => {
  return (
    <div>
      <p>orders {params.status}</p>
    </div>
  );
};

export default function App() {
  return (
    <div>
      <Link href="/orders/all">
        <a>All</a>
      </Link>
      <Switch>
        <Route path="/orders/all" component={AllOrders} />
        <Route path="/orders/:status" component={Orders} />
      </Switch>
    </div>
  );
}

We put the Route components in a Switch so that we only AllOrders or Orders but never both together.

In Orders , we get the URL parameters from the params prop.

Conclusion

We can add exclusive routes and watch for router changes in our React app with Wouter.

Categories
React

Add Routing to a React App with Wouter

Wouter is a library that lets us loading React components according to URLs.

In this article, we’ll look at how to add routing to a React app with Wouter.

Installation

To install it, we run:

npm i wouter

Getting Started

We can add routes to our React app by using the Route component.

For instance, we can write:

import React from "react";
import { Link, Route } from "wouter";

const InboxPage = () => <p>inbox</p>;

export default function App() {
  return (
    <div>
      <Link href="/users/1">
        <a className="link">Profile</a>
      </Link>

      <Route path="/about">About Us</Route>
      <Route path="/users/:name">
        {(params) => <div>Hello, {params.name}!</div>}
      </Route>
      <Route path="/inbox" component={InboxPage} />
    </div>
  );
}

We add the Router component to let us add map routes to React components.

:name is a URL parameter.

We can get its value with params.name .

Link lets us create links to load routes.

We can also add the component prop to add components as we did with InboxPage .

useRoute Hook

We can use the useRouter hook to extract URL parameters from a route.

For example, we write:

import React from "react";
import { Transition } from "react-transition-group";
import { useRoute } from "wouter";

export default function App() {
  const [match, params] = useRoute("/users/:id");

  return (
    <div>
      <Transition in={match}>
        <p>Hi, this is: {params.id}</p>
      </Transition>
    </div>
  );
}

We parse the route URL with tyhe useRoute hook by passing it into the hook.

Then we get the params object to get the URL parameters.

We also have the Transition component to add transitions when we load routes.

useLocation Hook

We can use the useLocation hook to return the location object to get go to a different route.

For example, we write:

import React from "react";
import { Link, Route, useLocation } from "wouter";

const InboxPage = () => {
  const [location, setLocation] = useLocation();

  return (
    <div>
      <p>{`The current page is: ${location}`}</p>
      <p>
        <a onClick={() => setLocation("/about")}>Click to update</a>
      </p>
    </div>
  );
};

export default function App() {
  return (
    <div>
      <Link href="/inbox">
        <a>Inbox</a>
      </Link>
      <Route path="/about">
        <p>About Us</p>
      </Route>
      <Route path="/users/:name">
        {(params) => <div>Hello, {params.name}!</div>}
      </Route>
      <Route path="/inbox" component={InboxPage} />
    </div>
  );
}

to add a link to the InboxPage component.

The link uses the useLocation hook’s setLocation function to go to the about route.

So when we click the Click to update link, we see About us.

Customizing the Location Hook

We can customize the useLocation hook to do what we want.

For instance, we can extract URL segments from the hash instead of from URL parameters.

To do this, we write:

import React, { useCallback, useEffect, useState } from "react";
import { Link, Route, Router, useLocation } from "wouter";

const currentLocation = () => {
  return window.location.hash.replace(/^#/, "") || "/";
};

const useHashLocation = () => {
  const [loc, setLoc] = useState(currentLocation());

  useEffect(() => {
    const handler = () => setLoc(currentLocation());
    window.addEventListener("hashchange", handler);
    return () => window.removeEventListener("hashchange", handler);
  }, []);
  const navigate = useCallback((to) => (window.location.hash = to), []);
  return [loc, navigate];
};

const InboxPage = () => {
  const [location] = useLocation();

  return (
    <div>
      <p>{`The current page is: ${location}`}</p>
    </div>
  );
};

export default function App() {
  return (
    <div>
      <Link href="/inbox">
        <a>Inbox</a>
      </Link>
      <Router hook={useHashLocation}>
        <Route path="/about">
          <p>About Us</p>
        </Route>
        <Route path="/users/:name">
          {(params) => <div>Hello, {params.name}!</div>}
        </Route>
        <Route path="/inbox" component={InboxPage} />
      </Router>
    </div>
  );
}

We have the currentLocation function that gets the part of the URL after thw hash.

Then we pass that into the useState hook to set the initial URL hash value.

And we listen for hashchange events to get the changes to the hash part of the URL.

If there are any changes, we return the loc location.

navigate navigates to the hash part of the URL.

Then we pass the hook into the hook prop to use it.

So now when we go to /#/users/1 , we see Hello, 1! displayed.

When we go to /#/inbox , we see The current page is: /inbox

And going to /#/about renders About Us .

Conclusion

Wouter is a lightweight router for React apps.

Categories
React

More Ways to Render Large Lists with React

We often have to render large lists in React apps.

In this article, we’ll look at ways to render large lists in React apps.

Virtual Scrolling

The react-virtualized library lets us add a virtualized list into our React app.

It only loads data that is shown on the screen.

To use it, we install it by running:

npm i react-virtualized

Then we can use it bu writing:

import React from "react";
import { List } from "react-virtualized";

export default function App() {
  const data = new Array(1000).fill().map((value, id) => ({
    id: id,
    title: id,
    body: id
  }));

  const renderRow = ({ index, key, style }) => (
    <div>
      <div key={key} style={style} className="post">
        <h3>{data[index].title}</h3>
        <p>item {data[index].body}</p>
      </div>
    </div>
  );
  return (
    <List
      width={window.innerWidth * 0.95}
      height={300}
      rowRenderer={renderRow}
      rowCount={data.length}
      rowHeight={120}
    />
  );
}

We just create the renderRiw function to render the items by their index and style .

The style is computed by react-virtualized to fit the data in the list.

We get the row from data[index] .

rowCount is set to the total row count.

height has the height of the list.

rowHeight has the row’s height.

react-window

The react-window is another virtualized scrolling component.

To install it, we run:

npm i react-window

Then we can add the list by writing:

import React from "react";
import { FixedSizeList as List } from "react-window";

export default function App() {
  const data = new Array(1000).fill().map((value, id) => ({
    id: id,
    title: id,
    body: id
  }));

  const Row = ({ index, key, style }) => (
    <div>
      <div key={key} style={style} className="post">
        <h3>{data[index].title}</h3>
        <p>item {data[index].body}</p>
      </div>
    </div>
  );
  return (
    <List
      width={window.innerWidth * 0.95}
      height={300}
      itemCount={data.length}
      itemSize={120}
    >
      {Row}
    </List>
  );
}

We have the Row component that has the row data.

It’s the same as the previous example’s renderRow function.

We have the List component with the width and height so it can compute the row sizes.

itemSize has the row’s height.

itemCount has the number of items to render.

react-window is faster and smaller than react-virtualized .

Conclusion

We can render large lists with virtual scrolling.