π₯’ A minimalist-friendly ~2.1KB routing for React and Preact
UNLICENSE License
Bot releases are hidden (Show)
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 #419ssrPath
, e.g. pass /home?a=b
to ssrPath
and it will pre-fill ssrSearch
#420href
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 #421flatMap()
method to flat child element in a switch. Thanks to abjfdi. See #254Published by molefrog 8 months ago
wouter
/wouter-preact
packages, because of the migration to monorepo in v3. This resulted in missing documentation on npmjs.org. This release fixes that.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 "*"
.
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>
/app
, this is equivalent to having a base path in your app./app/user/1
, /app/user/1/anything
and so on./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>
useSearch
hook and history state supportuseSearch
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()
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)" />
useBrowserLocation
and useHashLocation
hooksIn 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 routingThe 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 })
asChild
propOne 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>
useRoute("/:category/:page/*")
will automatically infer the type of route parameters./:wild*
use /*
or *
. The name of this parameter is always "*"
/app/*?
will match /app
, /app/
and /app/foo/bar
/: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"
)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.
Full changelog:
index.d.ts
now doesn't export types of methods that aren't present in the module #306 Thanks @Mati365wouter-preact
: Preact type declarations are now up-to-date with the main package, type exports have been fixed #309 #294 Thanks @robertknight and @jerssonMPublished 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:
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.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.
Published by molefrog almost 2 years ago
parent
prop in Router
, #270Published by molefrog almost 2 years ago
{ readonly [k in string]: string | undefined }
. This takes into account optional segments that can be undefined
#262, thanks @HansBrendeRoute
children: it does not allow mixing render props and other elements together #263, thanks @HansBrendeSwitch
now has less strict type of its children to allow conditional rendering and avoid confusion https://github.com/molefrog/wouter/commit/8f943abc72abb89f35b3cd6d2a57781106cef1b5
Published by molefrog almost 2 years ago
Link
component will now forward ref to the underlying a
element of custom component provided by user. @trymoto via #259useState
instead of useRef
for values that must be initialized only once. @HelKyle via #252 and #251πΊπ¦ We stand with Ukraine
Donate or spread the world to help Ukrainian soldiers and refugees
Published by molefrog about 3 years ago
Switch
not being able to process falsy children @jacobbuck via https://github.com/molefrog/wouter/pull/212
Published by molefrog over 3 years ago
makeMatcher
function, @shannonrothe via #182useLocation
not triggering the update with query string is changed, @wuzzeb via #178Published 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
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 #138events
is now exported from the use-location.js
module. @o-alexandrov via #129Happy wouting!
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!
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>
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 @davidje13Link
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:
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>
);
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
Published by molefrog about 5 years ago
npm install wouter-preact
#88, thanks @developituseLocation
now also catches updates that may happens between the initial render and useEffect
call. See #85Redirect
component now uses useLayoutEffect
to trigger the update as soon as possible and prevent apps from flicker.Published by molefrog about 5 years ago
Improvements:
wouter/preact
! Types are properly tested and can be maintained in the same repo. Thanks to @Ty3uK @StrayFromThePath and @polRknpm install wouter-preact
. This makes it possible to use proper peerDependecies
, and stop relying on React in your project #48 #49 cc/ @cedeberuseLocation
is now properly cached #74 #69 cc/ @nevessetLocation
now supports a 2nd argument which allows to perform replaceState
navigation (@Ty3uK via #52):setLocation('/about', true); // calls replaceState instead of pushState