Releases: molefrog/wouter
Bugfixes
Route parameters inheritance
Parameters provided by a Route
component or received from calling useParams
now also contain values inherited from parent nested routes. See #461 and #409.
Example:
// given the location is "/blog/1/users/4"
<Route path="/:page/:id" nest>
<Route path="/users/:id">
{/* calling `useParams()` here will return an object containing `{ page: "blog", id: "4" }`
{/* note: duplicate keys are overwritten by the inner-most route */}
</Route>
</Route>
RegExp paths support
Thanks to @JonahPlusPlus, you can now use regular expressions to define custom route patterns. This can be useful, for example, when you need additional validation of a route parameter. See #449
// this will only match numeric IDs, e.g. "/101", but not "/abc"
<Route path={/^[/](?<id>\d+)$/}>Hello</Route>
The example above uses named capturing groups to have id
available as a route parameter. However, you can also use regular groups and access these parameters via numeric indices:
// Route
/[/]([a-z]+)/
// Against "/foo", produces
{ 0: "foo" }
// Route
/[/](?<name>[a-z]+)/
// Against "/foo", produces
{ 0: "foo", name: "foo" }
Active links, hash location `href`, automatic `ssrSearch`
This release brings some small improvements:
Link
can now accept a function inclassName
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
tossrPath
and it will pre-fillssrSearch
#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 anhrefs
function defined on a router, but for built-inuseHashLocation
this is done automatically. See #421 - Minor improvement: use
flatMap()
method to flat child element in a switch. Thanks to abjfdi. See #254
v3.0.1 Missing docs on npmjs.org
- 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.
v3.0.0: Routing "all-inclusive"
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.
Below are some breaking changes that we tried to provide a migration guide for. To start using wouter v3, simply install:
> npm i [email protected]
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>
- This first route will be active for all paths that start with
/app
, this is equivalent to having a base path in your app. - The second one uses dynamic pattern to match paths like
/app/user/1
,/app/user/1/anything
and so on. - 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 anest
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, usememoryLocation
instead (see above)"wouter/use-location"
module has been renamed to"wouter/use-browser-location"
(see above)Router
no longer acceptsmatcher
prop. Useparser
props instead. Module '"wouter/matcher"` has been removed.Route
component can no longer use scope-bypassing absolute paths, e.g.<Route path="~/home" />
will no longer work.Router
will now always inherit base path, parser and other options from the parent router, henceparent
prop has been removed. Except for thehook
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.
v2.11.0: Better SSR support
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.
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
v2.10.1: Bugfixes
This release fixes missing export error caused by incorrect import of useInsertionEffect
hook in React <18. See #292
Thanks to @hudochenkov!
Other changes:
- Rollup plugin is no longer listed as dependency, #301
v2.10.0: `useSyncExternalStore`
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 fromwouter/use-location
for subscribing tolocation.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.
v2.9.1: Type fixes
- TypeScript declarations now include proper types for
parent
prop inRouter
, #270