Synchronizing with History
Stackflow's navigation logic does not rely on browser history by default. This is to support environments like React Native and NativeScript where the History API is not available. Therefore, to use browser history for navigation, you need to synchronize the stack state with the browser history. This functionality is provided by @stackflow/plugin-history-sync.
Install @stackflow/plugin-history-sync using the following command.
npm install @stackflow/plugin-history-syncOnce the installation is complete, declare routes in stackflow.config.ts and register the plugin in stackflow().
import { defineConfig } from "@stackflow/config";
export const config = defineConfig({
activities: [
{
name: "MyActivity",
route: "/my-activity",
},
{
name: "Article",
route: "/articles/:articleId",
},
],
transitionDuration: 350,
});import { stackflow } from "@stackflow/react";
import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic";
import { basicUIPlugin } from "@stackflow/plugin-basic-ui";
import { historySyncPlugin } from "@stackflow/plugin-history-sync";
import { config } from "./stackflow.config";
import MyActivity from "./MyActivity";
import Article from "./Article";
const { Stack, useFlow } = stackflow({
config,
components: {
MyActivity,
Article,
},
plugins: [
basicRendererPlugin(),
basicUIPlugin({
theme: "cupertino",
}),
historySyncPlugin({
config,
fallbackActivity: () => "MyActivity",
}),
],
});The historySyncPlugin accepts two options: config and fallbackActivity.
| Option | Type | Description |
|---|---|---|
| config | object | The config object created with defineConfig(). Routes are read from the route field of each activity definition. |
| fallbackActivity | function | Determines which activity to navigate to if there is no matching URL when first entering. Typically, you create a 404 page and assign it here. |
Warning - When mapping activity parameters to path parameters, ensure that the parameter values are URL-safe. If special characters that are not URL-safe are used, query parameters may appear duplicated.
Runtime Coercion of Activity Params
Regardless of how an activity is entered, including push(), replace(), pushStep(), replaceStep(), or URL arrival with or without a decode hook, the params you receive from useActivityParams() are always string | undefined at runtime.
// These two paths used to produce different runtime types. They don't anymore.
push("ArticleActivity", { visible: true }) // store: { visible: "true" }
// URL arrival: /articles/1?visible=true // store: { visible: "true" }The encode hook on a route still receives the original typed params, so you can use encode: ({ visible }) => ({ visible: visible ? "y" : "n" }) exactly as before. Coercion happens at the @stackflow/plugin-history-sync boundary, after encode has consumed the typed values to build the URL.
Migration note for decode users: if you previously relied on decode to inject typed values, such as decode: (p) => ({ count: Number(p.count) }), and read them back via useActivityParams().count as a number, that value is now a string in the store. Perform the type coercion at the usage site instead: Number(params.count).
In a server-side rendering environment, the window.location value is not available, so the initial activity cannot be determined. To set the initial activity, add the path value to the req.path field in the initialContext of the Stack as follows:
<Stack initialContext={{ req: { path: "/..." } }} />