wouter

πŸ₯’ A minimalist-friendly ~2.1KB routing for React and Preact

UNLICENSE License

Downloads
239.9K
Stars
6.1K
Committers
51

Bot releases are hidden (Show)

wouter - Active links, hash location `href`, automatic `ssrSearch` Latest Release

Published by molefrog 7 months ago

This release brings some small improvements:

  • Link can now accept a function in className for applying styles to currently active links. Read more #419
  • In SSR, search string can now be extracted directly from ssrPath, e.g. pass /home?a=b to ssrPath and it will pre-fill ssrSearch #420
  • Finally, after more than 2 years of waiting, links have proper href rendered when used with hash location. Before the fix, a link pointing to "/#users was rendered as <a href="/users" /> without a hash. This now can be controlled via an hrefs function defined on a router, but for built-in useHashLocation this is done automatically. See #421
  • Minor improvement: use flatMap() method to flat child element in a switch. Thanks to abjfdi. See #254
wouter - v3.0.1 Missing docs on npmjs.org

Published by molefrog 8 months ago

  • README.md file was not part of wouter/wouter-preact packages, because of the migration to monorepo in v3. This resulted in missing documentation on npmjs.org. This release fixes that.
wouter - v3.0.0-rc.2: Routing "all-inclusive"

Published by molefrog 11 months ago

Over the past four months, @jeetiss and I (@molefrog) have been working on the upcoming major release v3. We've carefully listened to your requests on features that you wanted to see in Wouter and used this feedback to design the new version’s architecture and API.

But everything comes with a cost, so now, the library is no longer 1.5Kb... it's 2.1Kb. But it's packed with tons of new features: nested routing, wildcard patterns, useSearch compatible with SSR, hash location, memory location, history state, useParams, more secure History patching, improved TypeScript types, and more! We have also improved tooling to make the development process faster and more bulletproof: replaced Jest with Vitest, added dozens of new test cases, improved the speed of type linting, and simplified the Preact build by introducing a monorepo.

We encourage you to start testing v3 in your projects right away by installing this Release Candidate version. Below are some breaking changes that we tried to provide a migration guide for. To start using wouter v3, simply install:

> npm i wouter@next

Note. We've updated regexparam dependency to the latest version, which ships with optional wildcards. If you've been using v3.0.0-rc.1 make sure you replace "wild" parameter with "*".

Nested routes

Previously, there was a hacky workaround to get nested routing working. You would have to manually inherit the router from the parent and customise the base path. This was far from ideal, and it only worked for static paths.

Now, nesting is a core feature of wouter and can be enabled on a route via the nest prop. When this prop is present, the route matches everything that starts with a given pattern and it creates a nested routing context. All child routes will receive location relative to that pattern. Let's take a look at this example:

<Route path="/app" nest>
  <Route path="/users/:id" nest>
    <Route path="/orders" />
  </Route>
</Route> 
  1. This first route will be active for all paths that start with /app, this is equivalent to having a base path in your app.
  2. The second one uses dynamic pattern to match paths like /app/user/1, /app/user/1/anything and so on.
  3. Finally, the inner-most route will only work for paths that look like /app/users/1/orders. The match is strict, since that route does not have a nest prop and it works as usual.

If you call useLocation() inside the last route, it will return /orders and not /app/users/1/orders. This creates a nice isolation and it makes it easier to make changes to parent route without worrying that the rest of the app will stop working. If you need to navigate to a top-level page however, you can use a prefix to refer to an absolute path:

<Route path="/payments" nest>
  <Route path="/all">
    <Link to="~/home">Back to Home</Link>
  </Route>
</Route> 

Native useSearch hook and history state support

useSearch hook is now part of the core. This means that you can import it from the main module, and will work with SSR too!

import { useSearch } from "wouter"

// returns "tab=settings&id=1"
// the hook for extracting search parameters is coming soon!
const searchString = useSearch() 

<Router ssrSearch={request.search}>
  {/* SSR! */}
</Router>

The long-awaited support for history state is also available now.

import { useLocation } from "wouter"

const [location, navigate] = useLocation()
navigate("/", { state: { user: 1 } });

// it works with Link and Redirect too!
<Link to="/" state={{ user: 1 }} />

// subscribing to state updates
import { useHistoryState } from "wouter/use-browser-location"

// this will only re-render when the actual state changes, not the URL
const state = useHistoryState()

Improved pattern matching

In this release, we are retiring our homemade route pattern parser in favour of regexparam, the optimised 394B library for transforming route patterns into regular expressions. Although, this change might break existing apps (please refer to the list of breaking changes below), it unlocks some nice features that would not have been possible to achieve in v2:

// matches literally anything
<Route path="*" /> 
<Route path="/books/*/:genre" />

// use optional wildcards to make the trailing slash optional
<Route path="/app/*?" />

// Parameter w/ Suffix 
<Route path="/movies/:title.(mp4|mov)" />

Built-in useBrowserLocation and useHashLocation hooks

In v2, in order to access the low-level location hook you had to import it from "wouter/use-location". The name of this file was confusing for many users, because of another function:

// V2 API

// returns current location scoped to the parent <Router hook={...} />
import { useLocation } from "wouter"

// low-level location hook, subscribes to current `location.pathname`
// these functions are not the same!
import useLocation from "wouter/use-location"

Unless you are writing an app that only subscribes to the current location and doesn't use other features such as routes, switches, params, base paths etc., you should use the later one.

We have renamed this module to "wouter/use-browser-location" and added the second essential hook "wouter/use-hash-location" for hash-based routing, so you won't need to reimplement it in your apps every time:

import { useHashLocation } from "wouter/use-hash-location"

<Router hook={useHashLocation} />

memoryLocation for testing and in-memory routing

The package no longer ships with "wouter/static-location" module, since this function wasn't well suited for SSR. Instead, there is now a new high-order hook called memoryLocation which can be used for in-memory routing or for testing.

import { memoryLocation } from "wouter/memory-location"

// in-memory router
const { hook } = memoryLocation() // initial path is "/" by default
<Router hook={hook} />

// `static` option makes it immutable, use in testing environments that don't support `location`
const { hook, navigate } = memoryLocation({ path: "/dashboard", static: true })
navigate("/users") // nothing happens

// `record` option for keeping the navigation history in `history` array
const { hook, navigate, history } = memoryLocation({ record: true })

Link composition with asChild prop

One of the notable changes is the behaviour of links that customise existing components or <a /> elements provided in children. We have thoroughly researched best practises from Next.js and Radix UI, and have decided to make link composition more explicit.

Now, Link will always wrap its children in an <a /> tag, unless asChild prop is provided:

// this no longer works! you will end up with double <a> elements
<Link to="/">
  <a className="link">Home</a>
</Link>

// use this instead
<Link to="/" asChild>
  <a className="link">Home</a>
</Link>

// this will still work as before
<Link to="/">Home</Link>

The full list of breaking changes

  • The minimum supported TypeScript version is 4.1 now. This was done mostly to avoid duplicated type definition files for versions that don't support automatic parameter inference from a path string, e.g. useRoute("/:category/:page/*") will automatically infer the type of route parameters.
  • Named wildcard segments are no longer supported. Instead of /:wild* use /* or *. The name of this parameter is always "*"
  • Use optional wildcards to define a wildcard segment with optional trailing slash, e.g. /app/*? will match /app, /app/ and /app/foo/bar
  • Plus sign modifiers /:user+ are no longer supported
  • "wouter/static-location" has been removed, use memoryLocation instead (see above)
  • "wouter/use-location" module has been renamed to "wouter/use-browser-location" (see above)
  • Router no longer accepts matcher prop. Use parser props instead. Module '"wouter/matcher"` has been removed.
  • Route component can no longer use absolute paths. <Route path="~/home" /> will no longer work.
  • Router will now always inherit base path, parser and other options from the parent router, hence parent prop has been removed. Except for the hook prop, which will case all other options to reset. This is done to achieve better isolation between nested apps that use different location subscriptions (e.g. hash-based router should not inherit base from the outer location-based router).
  • events export has been removed from "wouter/use-location" (this module is now called "wouter/use-browser-location")
  • Default exports have been removed from all modules
  • Absolute and relative path pre-processing is now part of wouter core. If you're writing a custom location hook, you no longer need to worry about handling base paths.
wouter - v2.11.0: Better SSR support

Published by molefrog over 1 year ago

In this version, we are introducing a new prop that you can pass to the top-level Router component: ssrPath. The migration to the useSyncExternalStore hook in v2.10.0 made it possible for us to use a native way of telling React what the location should be when rendering on the server.

Prior to this release, our users had to override the default location hook with wouter/static-location, which lacked a nice DX and could cause hydration warnings. We are deprecating the static location hook in favor of the new ssrPath prop. Rendering your app on the server is now as easy as:

const handleRequest = (req, res) => {
  // top-level Router is mandatory in SSR mode
  const prerendered = renderToString(
    <Router ssrPath={req.path}>
      <App />
    </Router>
  );

  // respond with prerendered html
};

You can find a detailed guide in the README. To see the new API in action, we have prepared a simple demo powered by Wouter and Ultra, a server-side rendering framework for Deno. Take a look at how the app is rendered on the server and then hydrated in the browser.

Demo: wultra.deno.dev

Full changelog:

  • SSR support in syncExternalStore #305
  • index.d.ts now doesn't export types of methods that aren't present in the module #306 Thanks @Mati365
  • Fix incorrect TS4.1 type exports #291 Thanks @tec27
  • wouter-preact: Preact type declarations are now up-to-date with the main package, type exports have been fixed #309 #294 Thanks @robertknight and @jerssonM
wouter - v2.10.1: Bugfixes

Published by molefrog over 1 year ago

This release fixes missing export error caused by incorrect import of useInsertionEffect hook in React <18. See https://github.com/molefrog/wouter/issues/292
Thanks to @hudochenkov!

Other changes:

wouter - v2.10.0: `useSyncExternalStore`

Published by molefrog over 1 year ago

In this alpha release, we're migrating useLocation hook to useSyncExternalStore under the hood (the new API for subscribing to external state sources compatible for concurrent mode).

This hook is available in React 18, for all earlier versions we've included a shim. We've also done some heavy refactoring which should lead to better performance and new features such as:

  • useSearch exported from wouter/use-location for subscribing to location.search
  • useLocationProperty for subscribing to arbitrary location updates.
  • Standalone navigate with stable reference and no dependency on current location, which means that your components that only perform navigation won't have unnecessary re-renders.
import { useSearch, useLocationProperty, navigate } from 'wouter/use-location';

// get all search params:
const search = useSearch();

// set all search params:
navigate("?name=john");

// get individual search param (will not trigger a re-render if other params change! πŸŽ‰ )
const nameValue = useLocationProperty(() => new URLSearchParams(window.location.search).get("name"));

// set individual search param:
const params = new URLSearchParams(window.location.search);
params.set("name", "john");
navigate("?" + params.toString()); 

Thanks @HansBrende for their contribution.

wouter - v2.9.1: Type fixes

Published by molefrog almost 2 years ago

  • TypeScript declarations now include proper types for parent prop in Router, #270
wouter - v2.8.1: TypeScript Improvements

Published by molefrog almost 2 years ago

  • When route parameters can be inferred from the path, they now have a type of { readonly [k in string]: string | undefined }. This takes into account optional segments that can be undefined #262, thanks @HansBrende
  • Proper typings of Route children: it does not allow mixing render props and other elements together #263, thanks @HansBrende
  • Switch now has less strict type of its children to allow conditional rendering and avoid confusion https://github.com/molefrog/wouter/commit/8f943abc72abb89f35b3cd6d2a57781106cef1b5
wouter - v2.8.0: It's been a while

Published by molefrog almost 2 years ago

  • Feature: Route parameters are now automatically inferred in TypeScript. @itsMapleLeaf via #211
  • Feature: Link component will now forward ref to the underlying a element of custom component provided by user. @trymoto via #259
  • Optimization: use useState instead of useRef for values that must be initialized only once. @HelKyle via #252 and #251
  • Bugfix: Prevent Link navigation when event was cancelled. @Tomas2D via #239

πŸ‡ΊπŸ‡¦ We stand with Ukraine
Donate or spread the world to help Ukrainian soldiers and refugees

wouter - Bug fixes & code size optimizations

Published by molefrog about 3 years ago

wouter - Bugfixes

Published by molefrog over 3 years ago

  • Fix incorrect type of the makeMatcher function, @shannonrothe via #182
  • Fix a bug with useLocation not triggering the update with query string is changed, @wuzzeb via #178
wouter -

Published by molefrog over 3 years ago

  • Fixed a regression bug introduced in v2.7.2: a path returned from useLocation() included a query string value e.g. "/foo?bar=baz", while it should have been just "/foo". See #170 for details.
wouter - v2.7.0: React 17 support and absolute routes within a subpath

Published by molefrog over 3 years ago

We're continuing to improve the library by keeping it small and simple, thanks to our wonderful contributors. First of all, it's the React 17 support: I'm sure many of you have already upgraded your project to the new version of React, which apparently "has no new features". Wouter now has proper peerDependencies that allow you to use it with React 17 thanks to @omgovich!

Another important update is that it's now possible to access the routes outside of the base scope when using a base parameter:

<Router base="/app">
  <Link href="/relative">Relative Link</Link>
  {/* 
      by prexing a path with tilda, you can reference a global route: when you click on this link 
      it navigates you to "/absolute", and not "/app/absolute" 
   */}
  <Link href="~/absolute">Absolute Link</Link>
</Router>

See https://github.com/molefrog/wouter/pull/153 for more info. Credits @jvdsande

wouter - Maintenance release

Published by molefrog almost 4 years ago

This patch release contains a few important bug-fixes:

  • base parameter in Router is now case-insensitive. Thanks @Veranete @cbbfcd via #138
  • Fixed a "func.apply is not a function" error when using a custom matcher. @cbbfcd via #137
  • The list of events is now exported from the use-location.js module. @o-alexandrov via #129

Happy wouting!

wouter - Dual publish

Published by molefrog about 4 years ago

This release aims to fix a problem when a subpath module require("wouter/static-location") can't be properly required from Node.js, thanks @davidje13 #126

Wouter now properly supports ESM and CommonJS environments and it can even work within .mjs scripts!

wouter - A route to extendability

Published by molefrog about 4 years ago

✨ The new release brings up several important updates!

  • The new project logo, thanks to Katya Simacheva

  • Released in 2.5.1 Wouter now targets ESM Node modules and properly supports CJS submodules. #126 thanks @davidje13

  • Using a default route in a Switch just became easier: use <Route /> component with no props and it will match everything that didn't match regular routes. @cbbfcd via #103

<Switch>
  <Route path="/users> ... </Route>
- <Route path="/:rest*">
+ <Route>
    Not Found!
  </Route>
</Switch>

check out the Demo β†’

  • Static location hook now supports record: option, which allows to save the history of location changes in hook.history. It improves the testing DX and allows to detect redirects in SSR responses. #113, thanks @davidje13
  • Link and Redirect components now can accept replace option that will tell wouter that replaceState navigation should be used. Example:
<Link to="/users" replace>Users</Link>
<Redirect to="/home" replace />
  • Some tiny updates in the README: table of contents for better navigation, more examples, testimonials! It's such an honor to see that wouter's community is getting bigger and developers trust it in production:
    image

  • The entire type definition codebase had been reworked! Types are updated to support latest features like default routes and replace navigation, and to support custom location hooks that might have specific navigational options. This change is especially important in terms of wouter's new strategy: supporting new features through external extendability see https://github.com/molefrog/wouter/issues/102#issuecomment-654694679.

This will allow us to easily implement custom location hooks, that can for example support state changes:

import { useLocation } from "wouter";
import useLocationWithState, { LocationWithState }  from '@wouter/use-location-state';

const NavigateWithState = () => {
  // useLocation is a generic function that can accept the custom location hook type
  const [location, update] = useLocation<LocationWithState>();
  
  const onClick = useCallback(() => {
    // this is now a valid function call, `state:` option is included in LocationWithState type
    update("/home", state: { foo: "bar" });
  }, [update]);

  return null;
}

const App = () => (
  <Router hook={useLocationWithState}>
    <Link<LocationWithState> to="/app" state={{ foo: 'bar' }}>Click Me!</Link>
  </Router>
);
wouter - First-class base path support!

Published by molefrog almost 5 years ago

Though the base path support was possible before, via a custom useLocation hook, it wasn't fully SEO-friendly β€” for example, the link would still have a relative href attribute.

After a long discussion, it's been decided to enable the base path support on a top-level Router component πŸŽ‰ You can now just wrap your app with <Router base="/app"> and that's it! Learn more about this feature β†’

The added bundle cost is just 48 extra bytes.

@omgovich @cbbfcd @guiem via #76

wouter - Preact 10.0, generic types and missing updates

Published by molefrog about 5 years ago

  • Wouter now is fully-compatible with Preact 10.0! You can install Preact version via npm install wouter-preact #88, thanks @developit
  • Type definitions now support generics, handy if you want to specify a shape of props your component accepts #87, thanks @ArnaudBarre
  • useLocation now also catches updates that may happens between the initial render and useEffect call. See #85
  • Redirect component now uses useLayoutEffect to trigger the update as soon as possible and prevent apps from flicker.
wouter - Types, types, types... and Preact-only package!

Published by molefrog about 5 years ago

Improvements:

  • TypeScript types are now bundled with the library, including types for React and wouter/preact! Types are properly tested and can be maintained in the same repo. Thanks to @Ty3uK @StrayFromThePath and @polRk
  • Wouter now comes with a standalone package with Preact-only version: npm install wouter-preact. This makes it possible to use proper peerDependecies, and stop relying on React in your project #48 #49 cc/ @cedeber
  • 2nd argument of useLocation is now properly cached #74 #69 cc/ @neves
  • Links do not fire navigation when click using modifier keys like ctrl or ⌘ #68 thanks @marvinhagemeister
  • setLocation now supports a 2nd argument which allows to perform replaceState navigation (@Ty3uK via #52):
setLocation('/about', true); // calls replaceState instead of pushState
wouter -

Published by molefrog over 5 years ago

Bugfixes:

  • Fix Route/Switch dynamic segments rendering bug #45 / @jeetiss