Skip to content

Releases: molefrog/wouter

Active links, hash location `href`, automatic `ssrSearch`

11 Mar 10:12
Compare
Choose a tag to compare

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

v3.0.1 Missing docs on npmjs.org

26 Feb 15:29
Compare
Choose a tag to compare
  • 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"

21 Nov 12:44
Compare
Choose a tag to compare

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鈥檚 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 wouter@3.0.0

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 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, 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.

v2.11.0: Better SSR support

17 May 12:43
Compare
Choose a tag to compare

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

v2.10.1: Bugfixes

18 Apr 16:03
Compare
Choose a tag to compare

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`

06 Feb 11:02
Compare
Choose a tag to compare

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.

v2.9.1: Type fixes

05 Jan 13:30
Compare
Choose a tag to compare
  • TypeScript declarations now include proper types for parent prop in Router, #270

v2.8.1: TypeScript Improvements

05 Nov 19:05
Compare
Choose a tag to compare
  • 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 8f943ab

v2.8.0: It's been a while

02 Nov 10:24
Compare
Choose a tag to compare
  • 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

Bug fixes & code size optimizations

22 Sep 17:32
Compare
Choose a tag to compare