# import { Callout } from "nextra-theme-docs"; import { APITable } from "../../components/APITable"; # `@stackflow/config` ### Config - Introduce the concept of `Config` in Stackflow. - Allows static declaration of activities without React dependency. You can write as follows. ```tsx showLineNumbers filename="stackflow.config.ts" copy import { defineConfig } from "@stackflow/config"; export const config = defineConfig({ activities: [ { name: "HomeActivity", }, { name: "MyProfileActivity", } ], transitionDuration: 270, }); ``` Now, when additional configuration is needed in a plugin, you can extend `@stackflow/config` to receive the necessary information for the plugin's operation, not just through the plugin function's parameters. ### `decorate` API You can extend the `Config` type as follows to utilize `config` in various places. ```typescript declare module "@stackflow/config" { interface Config> { relayEnvironment: RelayEnvironment; } } config.decorate("relayEnvironment", myRelayEnvironment); ``` ### API | | | | | ------------------ | ----------------- | -------------------------------------------------- | | activities | `ActivityDefinition[]` | An array of activities. | | transitionDuration | `number` | Duration of the transition animation. | | initialActivity | `() => ActivityDefinition["name"]` | The initial activity. | # import { APITable } from '../../../components/APITable' # `@stackflow/link` It mimics the `` component behavior provided by [Gatsby](https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-link/) or [Next.js](https://nextjs.org/docs/app/api-reference/components/link). ## Dependencies It can be used only when [`@stackflow/plugin-history-sync`](/api-references/plugins/plugin-history-sync) is set. ## Installation ```bash npm2yarn copy npm install @stackflow/link ``` ## Usage Import `Link` directly from `@stackflow/link`. ```tsx showLineNumbers filename="MyComponent.tsx" copy import { Link } from "@stackflow/link"; const MyComponent = () => { return (
{/* ... */}
) } ``` ## Reference | | | | | ----------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | activityName | `string` | The name of the activity you want to link to. It's used to determine which route to navigate. | | activityParams | `object` | Parameters to be passed to the activity. These parameters will be used to fill the route pattern. | | animate | `boolean` (optional) | Indicates whether to animate the transition when navigating. If not provided, the default transition is used. | | replace | `boolean` (optional) | If true, replaces the current entry in the history stack instead of pushing a new entry. | | ref | `React.RefObject` (optional) | A reference to the underlying anchor element, allowing direct DOM access if needed. | | onClick | `function` (optional) | Function to handle the click event on the link. You can use it to perform additional actions on link clicks. | | children | `React.ReactNode` | The content to be rendered inside the link. This is typically text or other elements the user can interact with. | # import { APITable } from "../../../components/APITable"; # `@stackflow/plugin-basic-ui` Render the UI within the activity using the global stack state. It provides `cupertino` and `android` themes by default. ## Installation ```bash npm2yarn copy npm install @stackflow/plugin-basic-ui ``` ## Usage Provides components in the form of application app bars. ```ts showLineNumbers filename="stackflow.config.ts" copy import { defineConfig } from "@stackflow/config"; export const config = defineConfig({ activities: [ { name: "MyHome", route: "/", }, { name: "MyArticle", route: "/articles/:articleId", }, ], }); ``` ```tsx showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; import { config } from "./stackflow.config"; import { MyHome } from "./MyHome"; import { MyArticle } from "./MyArticle"; const { Stack } = stackflow({ config, components: { MyHome, MyArticle, }, plugins: [ // ... basicUIPlugin({ theme: "cupertino", }), ], }); ``` ### `basicUIPlugin` Options | | | | | ---- | ---- | ---- | | theme | `cupertino` \| `android` | Set the theme. | | rootClassName | `string`(optional) | Set the root class name. | | appBar | `AppBar`(optional) | Set the app bar. | ```tsx filename="AppScreen" copy import { AppScreen } from "@stackflow/plugin-basic-ui"; const Something = () => { return (
Hello, World
); }; ``` ### `appBar` | | | | | ---- | ---- | ---- | | backButton | `{ renderIcon?: () => ReactNode; ariaLabel?: string; onClick?: (e) => void }` \| `{ render?: () => ReactNode }` | Set the back button. | | closeButton | `{ renderIcon?: () => ReactNode; ariaLabel?: string; onClick?: (e) => void }` \| `{ render?: () => ReactNode }` | Set the close button. | It also provides modal and bottom sheet components. ```tsx filename="Modal" copy import { Modal } from "@stackflow/plugin-basic-ui"; const Something = () => { return (
Hello, World
); }; ``` ```tsx filename="BottomSheet" copy import { BottomSheet } from "@stackflow/plugin-basic-ui"; const Something = () => { return (
Hello, World
); }; ``` # # `@stackflow/plugin-devtools` This plugin is used as a data storage layer for the Stackflow Devtools. ## Installation ```bash npm2yarn copy npm install @stackflow/plugin-devtools ``` ## Usage To utilize the plugin, integrate it within your Stackflow setup as shown: ```ts showLineNumbers filename="stackflow.config.ts" copy import { defineConfig } from "@stackflow/config"; export const config = defineConfig({ activities: [ { name: "MyHome", route: "/", }, { name: "MyArticle", route: "/articles/:articleId", }, ], }); ``` ```tsx showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { devtoolsPlugin } from "@stackflow/plugin-devtools"; import { config } from "./stackflow.config"; import { MyHome } from "./MyHome"; import { MyArticle } from "./MyArticle"; const { Stack } = stackflow({ config, components: { MyHome, MyArticle, }, plugins: [ devtoolsPlugin(), // ... ], }); ``` # import { Callout } from "nextra-theme-docs"; # `@stackflow/plugin-google-analytics-4` This plugin is used to integrate Google Analytics 4 with Stackflow. ## Inatallation ```bash npm2yarn copy npm install @stackflow/plugin-google-analytics-4 ``` ## Usage ### Initialize ```ts showLineNumbers filename="stackflow.config.ts" copy import { defineConfig } from "@stackflow/config"; export const config = defineConfig({ activities: [ { name: "MyHome", route: "/", }, { name: "MyArticle", route: "/articles/:articleId", }, ], }); ``` ```tsx showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { googleAnalyticsPlugin } from "@stackflow/plugin-google-analytics-4"; import { config } from "./stackflow.config"; import { MyHome } from "./MyHome"; import { MyArticle } from "./MyArticle"; const { Stack } = stackflow({ config, components: { MyHome, MyArticle, }, plugins: [ googleAnalyticsPlugin({ trackingId: "G-XXXXXXXXXX", // Required. Your Google Analytics 4 Tracking ID userInfo: { // optional userId: "test123", // Your own user distinguishable id. (https://bit.ly/3VGu04K) userProperties: { // ... // You can add additional event parameters. This value will collected as a user properties of "Custom Dimension" in GA. // https://bit.ly/3uQbriR }, }, useTitle: true, // Optional. If true, the title of the current screen will be sent to GA. if false, ActivityName will be sent to GA.(default false). }), ], }); ``` ### Set config ```tsx showLineNumbers filename="App.tsx" copy import { useEffect } from "react"; import { useGoogleAnalyticsContext } from "@stackflow/plugin-google-analytics-4"; const App = () => { const { setConfig } = useGoogleAnalyticsContext(); useEffect(() => { setConfig({ user_id: "test123", user_properties: { // ... }, // ... // GA4 config values. // https://bit.ly/3Y7IXhV }); }, [setConfig]); return
...
; }; ``` ### send event Every stack has wrapped as a context, you can use `useGoogleAnalyticsContext` hook to send event. ```tsx showLineNumbers filename="AdCreateButton.tsx" copy import { useGoogleAnalyticsContext } from "@stackflow/plugin-google-analytics-4"; // ... const { sendEvent } = useGoogleAnalyticsContext(); return ( <> ); ``` Here's an example capture of custom GA4 event sent from the above code. Note that the second parameter object is sent as a custom event parameter. ![image](https://user-images.githubusercontent.com/29659112/206271251-91f63efa-0583-4846-b4d5-79ed2ff0a881.png) FAQ: **Pageview event is triggered twice.** Unckeck "Page changes based on browser history events" in GA4 settings (**_Web Stream>Enhanced Measurement>Pageviews>Advanced_**) This plugin trigger pageview event manually using stackflow's "[Effect Hook](/docs/advanced/write-plugin#effect-hooks)". You don't have to trigger it again. ![image](https://user-images.githubusercontent.com/29659112/206275171-57270f54-ac1c-4e0d-b58c-916a842c99b8.png) # import { APITable } from "../../../components/APITable"; # `@stackflow/plugin-history-sync` Stackflow does not use the browser's history by default. This plugin synchronizes the stack state with the current browser's history. ## Installation ```bash npm2yarn copy npm install @stackflow/plugin-history-sync ``` ## Usage ```ts showLineNumbers filename="stackflow.config.ts" copy import { defineConfig } from "@stackflow/config"; export const config = defineConfig({ activities: [ { name: "MyHome", route: "/", }, { name: "MyArticle", route: "/articles/:articleId", }, { name: "NotFoundPage", route: "/404", }, ], }); ``` ```ts showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { historySyncPlugin } from "@stackflow/plugin-history-sync"; import { config } from "./stackflow.config"; import { MyHome } from "./MyHome"; import { MyArticle } from "./MyArticle"; import { NotFoundPage } from "./NotFoundPage"; const { Stack } = stackflow({ config, components: { MyHome, MyArticle, NotFoundPage, }, plugins: [ // ... historySyncPlugin({ config, /** * If a URL that does not correspond to the URL template is given, it moves to the `fallbackActivity`. */ fallbackActivity: ({ initialContext }) => "NotFoundPage", /** * Uses the hash portion of the URL (i.e. window.location.hash) */ useHash: false, }), ], }); ``` ## Reference | | | | | ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | config | `object` | The config object created with `defineConfig()`. Routes are read from the `route` field of each activity definition. | | fallbackActivity | `(args: { initialContext: any }) => K` | 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. | | useHash | `boolean` (optional) | Determines if hash-based routing should be used. Defaults to false. | | history | `History` (optional) | A custom history object used for managing navigation state. Defaults to browser or memory history. | | urlPatternOptions | `UrlPatternOptions` (optional) | Options for URL pattern matching and generation, affecting how URLs are constructed and parsed. | # # `@stackflow/plugin-renderer-basic` This plugin is used to render the activity that should be rendered by default using the stack state. ## Installation ```bash npm2yarn copy npm install @stackflow/plugin-renderer-basic ``` ## Usage ```ts showLineNumbers filename="stackflow.config.ts" copy import { defineConfig } from "@stackflow/config"; export const config = defineConfig({ activities: [ { name: "MyHome", route: "/", }, { name: "MyArticle", route: "/articles/:articleId", }, ], }); ``` ```ts showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; import { config } from "./stackflow.config"; import { MyHome } from "./MyHome"; import { MyArticle } from "./MyArticle"; const { Stack } = stackflow({ config, components: { MyHome, MyArticle, }, plugins: [basicRendererPlugin()], }); ``` # # `@stackflow/plugin-renderer-web` Render active activity only using the stack state. this plugins can be used for web application to be served on the web browser ## Installation ```bash npm2yarn copy npm install @stackflow/plugin-renderer-web ``` ## Usage ```ts showLineNumbers filename="stackflow.config.ts" copy import { defineConfig } from "@stackflow/config"; export const config = defineConfig({ activities: [ { name: "MyHome", route: "/", }, { name: "MyArticle", route: "/articles/:articleId", }, ], }); ``` ```ts showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { webRendererPlugin } from "@stackflow/plugin-renderer-web"; import { config } from "./stackflow.config"; import { MyHome } from "./MyHome"; import { MyArticle } from "./MyArticle"; const { Stack } = stackflow({ config, components: { MyHome, MyArticle, }, plugins: [webRendererPlugin()], }); ``` # import { APITable } from "../../../components/APITable"; # `@stackflow/plugin-stack-depth-change` This plugin is useful when you want to monitor the depth of the stack. ## Installation ```bash npm2yarn copy npm install @stackflow/plugin-stack-depth-change ``` ## Usage ```ts showLineNumbers filename="stackflow.config.ts" copy import { defineConfig } from "@stackflow/config"; export const config = defineConfig({ activities: [ { name: "MyHome", route: "/", }, { name: "MyArticle", route: "/articles/:articleId", }, ], }); ``` ```ts showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { stackDepthChangePlugin } from "@stackflow/plugin-stack-depth-change"; import { config } from "./stackflow.config"; import { MyHome } from "./MyHome"; import { MyArticle } from "./MyArticle"; const { Stack } = stackflow({ config, components: { MyHome, MyArticle, }, plugins: [ // ... stackDepthChangePlugin({ onInit: ({ depth, activities, activeActivities }) => {}, onDepthChanged: ({ depth, activities, activeActivities }) => {}, }), ], }); ``` ## Reference ### `StackDepthChangePluginOptions` | | | | | --------------- | ---------------------------------- | -------------------------------------------------------------------------- | | onInit | `(args: StackDepthChangePluginArgs) => void` (optional) | Callback function to be invoked during plugin initialization with the current stack depth and activities. | | onDepthChanged | `(args: StackDepthChangePluginArgs) => void` (optional) | Callback function to be triggered whenever there is a change in the stack depth. | ### `StackDepthChangePluginArgs` | | | | | --------------- | ---------------------------------- | -------------------------------------------------------------------------- | | depth | `number` | The current depth of the active activities stack. | | activities | `Activity[]` | An array of all activities currently in the stack. | | activeActivities| `Activity[]` | An array of activities that are active (either "exit-active" or "enter-done"). | # import { APIPipeLiningDiagram } from "../../../components/diagrams/APIPipeLiningDiagram"; # API Pipelining As shown above, you can reduce the time taken for initial rendering by simultaneously initializing the React app and making API requests in the entry file. By utilizing the Stackflow Loader API, you can implement API pipelining in a clean manner. ```tsx showLineNumbers filename="entry.ts" copy import { makeTemplate } from "@stackflow/plugin-history-sync"; import { config } from "./stackflow/stackflow.config"; async function main() { let initialLoaderData: unknown | null = null; for (const activity of config.activities) { const t = makeTemplate({ path: activity.route }); const match = t.parse(location.pathname + location.search); if (!match) { continue; } // 1. Request API data (do not await) initialLoaderData = activity.loader({ params: match as any }); break; } // 2. Download the React app simultaneously. const { renderApp } = await import("./renderApp"); // 3. Combine them. renderApp({ initialLoaderData }); } main(); ``` By passing initialLoaderData to Stack, it overrides the result with the received loaderData instead of executing the first loader. ```tsx showLineNumbers filename="renderApp.ts" copy export function renderApp({ initialLoaderData }: { initialLoaderData: unknown }) { const root = ReactDOM.createRoot(document.getElementById("root")!); root.render( // Error and loading handling is possible in React ); } ``` # # Code Splitting To properly render transition effects after splitting code by activity, you need to set it up as follows. ```tsx // as-is: import { lazy } from "react"; import { stackflow } from "@stackflow/react"; stackflow({ // ... components: { MyActivity: lazy(() => import("./activities/MyActivity")), }, }); // to-be: import { stackflow, lazy } from "@stackflow/react"; stackflow({ // ... components: { // replace `lazy()` from `@stackflow/react` MyActivity: lazy(() => import("./activities/MyActivity")), }, }); ``` This is to pause the stack state mutating while fetching the corresponding JS asset (while the Promise is pending), and resume the state mutating again once the loading is complete. # import { Callout } from "nextra-theme-docs"; import { APITable } from "../../../components/APITable"; # 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. ```sh npm2yarn copy npm install @stackflow/plugin-history-sync ``` Once the installation is complete, declare routes in `stackflow.config.ts` and register the plugin in `stackflow()`. ```tsx showLineNumbers filename="stackflow.config.ts" copy import { defineConfig } from "@stackflow/config"; export const config = defineConfig({ activities: [ { name: "MyActivity", route: "/my-activity", }, { name: "Article", route: "/articles/:articleId", }, ], transitionDuration: 350, }); ``` ```tsx showLineNumbers filename="stackflow.ts" copy 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 } = stackflow({ config, components: { MyActivity, Article, }, plugins: [ basicRendererPlugin(), basicUIPlugin({ theme: "cupertino", }), historySyncPlugin({ config, fallbackActivity: () => "MyActivity", }), ], }); ``` The `historySyncPlugin` accepts two options: `config` and `fallbackActivity`. | | | | | ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 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. 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: ```tsx ``` # # Loader API You can reduce the time taken for initial rendering by preloading data before navigating to an activity. Stackflow's built-in Loader API makes this straightforward. ## Defining a Loader Define a loader for your activity in `stackflow.config.ts`. The loader runs before the activity renders and its data is accessible via `useLoaderData()`. ```tsx showLineNumbers filename="HomeActivity.loader.ts" copy import type { ActivityLoaderArgs } from "@stackflow/config"; export async function homeActivityLoader({ params }: ActivityLoaderArgs<"HomeActivity">) { const data = await fetchData(params); return { data }; } ``` ```tsx showLineNumbers filename="stackflow.config.ts" copy {8} import { defineConfig } from "@stackflow/config"; import { homeActivityLoader } from "./HomeActivity.loader"; export const config = defineConfig({ activities: [ { name: "HomeActivity", route: "/", loader: homeActivityLoader, }, ], transitionDuration: 350, }); ``` Access the loader data inside the activity component: ```tsx showLineNumbers filename="HomeActivity.tsx" copy import { useLoaderData, type ActivityComponentType } from "@stackflow/react"; import type { homeActivityLoader } from "./HomeActivity.loader"; const HomeActivity: ActivityComponentType<"HomeActivity"> = () => { const loaderData = useLoaderData(); return (
{/* use loaderData */}
); }; ``` ## API Pipelining For advanced use cases, you can further reduce loading time by initiating API requests in parallel with React app initialization. See the [API Pipelining](/docs/advanced/api-pipelining) guide for details. # # Structured Activity A **Structured Activity** separates an activity into four distinct concerns: content, layout, loading state, and error handling. This makes it easy to apply code splitting, Suspense-based loading, and error boundaries β€” without wiring them up manually. ## Basic Usage Use `structuredActivityComponent()` instead of a plain React component when registering an activity. ```tsx showLineNumbers filename="Article.tsx" copy import { structuredActivityComponent } from "@stackflow/react"; declare module "@stackflow/config" { interface Register { Article: { articleId: number; title?: string; }; } } export const Article = structuredActivityComponent<"Article">({ content: ArticleContent, }); ``` Then register it in `stackflow()` the same way as a regular component: ```tsx showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { config } from "./stackflow.config"; import { Article } from "./Article"; export const { Stack } = stackflow({ config, components: { Article, }, plugins: [...], }); ``` ## Code Splitting Pass an async import as `content` to code-split the activity. Stackflow pauses stack state updates while the bundle loads, then resumes once it's ready β€” so transitions always feel correct. ```tsx showLineNumbers filename="Article.tsx" copy {5} export const Article = structuredActivityComponent<"Article">({ content: () => import("./Article.content"), }); ``` `Article.content.tsx` exports a `content()` helper: ```tsx showLineNumbers filename="Article.content.tsx" copy import { content } from "@stackflow/react"; const ArticleContent = content<"Article">(({ params: { title } }) => { return (

{title}

); }); export default ArticleContent; ``` ## Loading State Provide a `loading` component to show while the content bundle or loader data is being fetched. It renders as the Suspense fallback. ```tsx showLineNumbers filename="Article.loading.tsx" copy import { loading } from "@stackflow/react"; const ArticleLoading = loading<"Article">(() => { return
Loading...
; }); export default ArticleLoading; ``` ```tsx showLineNumbers filename="Article.tsx" copy {2,6} import { structuredActivityComponent } from "@stackflow/react"; import ArticleLoading from "./Article.loading"; export const Article = structuredActivityComponent<"Article">({ content: () => import("./Article.content"), loading: ArticleLoading, }); ``` ## Layout Provide a `layout` component to wrap the content. It receives `params` and `children`, making it easy to build consistent app bars or shell UIs that are available immediately β€” even while content is still loading. ```tsx showLineNumbers filename="Article.layout.tsx" copy import { layout } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; const ArticleLayout = layout<"Article">(({ params: { title }, children }) => { return ( {children} ); }); export default ArticleLayout; ``` ```tsx showLineNumbers filename="Article.tsx" copy {2,7} import { structuredActivityComponent } from "@stackflow/react"; import ArticleLayout from "./Article.layout"; import ArticleLoading from "./Article.loading"; export const Article = structuredActivityComponent<"Article">({ content: () => import("./Article.content"), layout: ArticleLayout, loading: ArticleLoading, }); ``` The render order is: `Layout` wraps `ErrorHandler` wraps `Suspense(Loading)` wraps `Content`. ## Error Handling Provide an `errorHandler` component to show when content throws. It receives the error and a `reset()` function to retry. ```tsx showLineNumbers filename="Article.tsx" copy import { structuredActivityComponent, errorHandler } from "@stackflow/react"; import ArticleLayout from "./Article.layout"; import ArticleLoading from "./Article.loading"; const ArticleError = errorHandler<"Article">(({ error, reset }) => { return (

Something went wrong.

); }); export const Article = structuredActivityComponent<"Article">({ content: () => import("./Article.content"), layout: ArticleLayout, loading: ArticleLoading, errorHandler: ArticleError, }); ``` If you need a custom error boundary implementation (e.g. to integrate with an error reporting service), pass it via the `boundary` option: ```tsx import { errorHandler } from "@stackflow/react"; import type { CustomErrorBoundary } from "@stackflow/react"; const MyErrorBoundary: CustomErrorBoundary = ({ children, renderFallback }) => { // your custom boundary logic }; const ArticleError = errorHandler<"Article">( ({ error, reset }) =>
...
, { boundary: MyErrorBoundary }, ); ``` ## With Loader API Structured activities work seamlessly with the [Loader API](/docs/advanced/preloading). Define the loader in `stackflow.config.ts` and use `useLoaderData()` inside `content()`. ```tsx showLineNumbers filename="Article.loader.ts" copy import type { ActivityLoaderArgs } from "@stackflow/config"; export async function articleLoader({ params }: ActivityLoaderArgs<"Article">) { const data = await fetchArticle(params.articleId); return { data }; } ``` ```tsx showLineNumbers filename="stackflow.config.ts" copy {2,9} import { defineConfig } from "@stackflow/config"; import { articleLoader } from "./Article.loader"; export const config = defineConfig({ activities: [ { name: "Article", route: "/articles/:articleId", loader: articleLoader, }, ], transitionDuration: 350, }); ``` ```tsx showLineNumbers filename="Article.content.tsx" copy import { content, useLoaderData } from "@stackflow/react"; import type { articleLoader } from "./Article.loader"; const ArticleContent = content<"Article">(({ params: { title } }) => { const { data } = useLoaderData(); return (

{title}

{/* use data */}
); }); export default ArticleContent; ``` ## Recommended File Structure Co-locating the pieces by activity keeps things easy to navigate: ``` activities/ └── Article/ β”œβ”€β”€ Article.tsx # structuredActivityComponent definition β”œβ”€β”€ Article.content.tsx # content() β”œβ”€β”€ Article.layout.tsx # layout() β”œβ”€β”€ Article.loading.tsx # loading() └── Article.loader.ts # loader ``` # import { Callout } from "nextra-theme-docs"; import { EffectHookDiagram } from "../../../components/diagrams/EffectHookDiagram"; import { APITable } from "../../../components/APITable"; # Write Your Own Plugin **Stackflow** helps you easily integrate extension logic written by others into your application through the plugin interface. Solve your problems with plugins and wrap them nicely to share with others. ## Making a Preset The easiest way to publish a **Stackflow** plugin is to provide it as a preset by combining plugins made by others. You can create your own preset by grouping multiple plugins into an array as shown below. ```ts showLineNumbers filename="stackflow.ts" copy import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; import { historySyncPlugin } from "@stackflow/plugin-history-sync"; import { stackflow } from "@stackflow/react"; const myPlugin = ({ ... }) => [ basicRendererPlugin(), historySyncPlugin({ ... }), ]; stackflow({ // ... plugins: [myPlugin()], }); ``` ## Basic Interface Plugin must return the following values as a function. | | | | | ---- | -------- | ---------------------------------------------------------------------------------------------------------- | | key | `string` | A unique string value assigned as the `key` when the plugin is rendered within the React tree as an array. | To try developing your first plugin for your app, start with an inline function as shown below. ```ts showLineNumbers filename="stackflow.ts" copy import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; import { stackflow } from "@stackflow/react"; stackflow({ // ... plugins: [ basicRendererPlugin(), basicUIPlugin({ theme: "cupertino", }), () => { return { key: "my-plugin", }; }, ], }); ``` ### Adding a render method If you want to add a new rendering, you can use the `render` API. Utilize the stack state to decide how to optimize the DOM or, if necessary, overlay a new DOM tree using the `render` API. ```tsx showLineNumbers filename="stackflow.ts" copy stackflow({ // ... plugins: [ () => { return { key: "my-plugin", render({ stack }) { return (
{stack.render().activities.map((activity) => (
{activity.render()}
))}
); }, }; }, ], }); ``` You can also override the stack state passed to the UI as shown below. ```ts showLineNumbers filename="stackflow.ts" copy {12,17} stackflow({ // ... plugins: [ () => { return { key: "my-plugin", render({ stack }) { return (
{stack .render({ // You can override the stack state here. }) .activities.map((activity) => (
{activity.render({ // You can override the activity state here. })}
))}
); }, }; }, ], }); ``` The overridden state value does not affect the main stack, but only applies to the `useStack()` and `useActivity()` within the rendering subtree of React. ### Wrapping Stack If you want to add a Context API Provider at the top level or wrap the top level with a specific DOM, use the `wrapStack` interface. ```ts showLineNumbers filename="stackflow.ts" copy {8} stackflow({ // ... plugins: [ () => { return { key: "my-plugin", wrapStack({ stack }) { // you can use the stack information brought in as an argument here return
{stack.render()}
; }, }; }, ], }); ``` **Caution** - The `wrapStack` API is applied to all renderings. Be careful as unintended side effects may occur. ### Wrapping Activity If you want to add a Context API Provider to each activity or wrap it with a specific DOM, use the `wrapActivity` interface. ```ts showLineNumbers filename="stackflow.ts" copy {8} stackflow({ // ... plugins: [ () => { return { key: "my-plugin", wrapActivity({ activity }) { // you can use the activity information brought in as an argument here return
{activity.render()}
; }, }; }, ], }); ``` **Caution** - The `wrapActivity` API is applied to all renderings. Be careful as unintended side effects may occur. ### Injecting Behavior at Initialization Use the `onInit` hook to call logic when the `` component is first initialized. ```ts showLineNumbers filename="stackflow.ts" copy {7-9} stackflow({ // ... plugins: [ () => { return { key: "my-plugin", onInit() { console.log("Initialized!"); }, }; }, ], }); ``` **Caution** - In React 18 and `React.StrictMode`, it may be called twice, so be careful to avoid unintended side effects. ## Effect Hooks Do you want to extend functionality or synchronize with external states? You can perform specific actions whenever the stack state changes or call functions before the stack state changes. The stack state begins to change when push, replace, or pop is called due to user actions. From that point, you can perform specific actions before the stack state changes (Pre-effect) and after the stack state has changed and the UI has been updated (Post-effect). ### Post-effects Post-effect hooks include `onPushed`, `onReplaced`, `onPopped`, and `onChanged`. Post-effect hooks are called after the UI has been fully updated, so you cannot undo or cancel the changes. Post-effect hooks can use the following arguments. | | | | | --------------------- | ---------- | ------------------------------------------ | | actions.getStack | `function` | Get the current stack state. | | actions.dispatchEvent | `function` | Add a new event to the core. | | effect | `object` | The effect that triggered the effect hook. | ```ts showLineNumbers filename="stackflow.ts" copy stackflow({ // ... plugins: [ () => { return { key: "my-plugin", onPushed(actions, effect) { // you can utilize // actions.getStack() // actions.dispatchEvent(...) console.log("Pushed!"); console.log("Effect:", effect); }, }; }, ], }); ``` The `onChanged` hook is called whenever the stack state changes without distinction. Therefore, if used together with `onPushed`, `onReplaced`, and `onPopped`, both effect hooks will be called. **Caution** - If you dispatch an event that triggers the effect within the effect hook, it can cause an infinite loop. Use `actions.dispatchEvent()` with caution. ### Pre-effects Pre-effect hooks include `onBeforePush`, `onBeforeReplace`, and `onBeforePop`. Pre-effect hooks are called before the event is delivered to the Core, allowing you to cancel the event. Pre-effect hooks can use the following arguments. | | | | | ---------------------- | ---------- | ------------------------------------------ | | actions.preventDefault | `function` | Cancel the default behavior. | | actions.isPrevented | `function` | Check whether the action is prevented. | | actions.getStack | `function` | Get the current stack state. | | actions.dispatchEvent | `function` | Add a new event to the core. | | effect | `object` | The effect that triggered the effect hook. | Calling `preventDefault()` cancels the default event dispatch, but it does not stop the remaining pre-effect hooks. Those hooks can call `isPrevented()` to observe the current action-local state. ## Determining initial activity You can override the existing `initialActivity` behavior through the `overrideInitialEvents` API. ```ts showLineNumbers filename="stackflow.ts" copy {1, 9-19} import { makeEvent } from "@stackflow/core"; stackflow({ // ... plugins: [ () => { return { key: "my-plugin", overrideInitialEvents({ initialEvents }) { if (initialEvents.length > 0) { return initialEvents; } return [ makeEvent("Pushed", { // ... }), ]; }, }; }, ], }); ``` # LLMs.txt # LLMs.txt We provide an LLMs.txt setup to help large language models (LLMs) easily understand Stackflow. ### Structure We provide the following LLMs.txt files: - [llms.txt](https://stackflow.so/llms.txt): The main file that describes the structure of all LLMs.txt files. - [llms-full.txt](https://stackflow.so/llms-full.txt): Includes all Stackflow documentation. - [llms-changelog.txt](https://stackflow.so/llms-changelog.txt): Contains the latest updates and changes so you can review version-by-version history. ### Using with AI tools #### Cursor You can include the LLMs.txt files in your project using Cursor's @Docs feature. [**Learn more about Cursor @Docs**](https://docs.cursor.com/context/@-symbols/@-docs) # Changelog import ChangelogContent from "../../components/ChangelogContent.mdx" # import { Callout } from "nextra-theme-docs"; import { ActivityDiagram } from "../../../components/diagrams/ActivityDiagram"; import { APITable } from "../../../components/APITable"; # Activity **Activity** is a single screen that gets stacked one by one. Activities have the following properties and can be accessed using the `useActivity()` hook if needed. | | | | | --------------- | -------------------------------------------------------- | -------------------------------------------------------- | | id | `string` | Unique ID value for each activated activity screen | | name | `string` | Registerd activity name | | transitionState | `enter-active`, `enter-done`, `exit-active`, `exit-done` | Transition state of current activity | ## Registering an Activity To use an activity, first declare it in `stackflow.config.ts` and register the React component in `stackflow()`. Declare the activity's parameter types using module augmentation: ```typescript showLineNumbers filename="MyActivity.tsx" copy declare module "@stackflow/config" { interface Register { MyActivity: { // activity has no parameters }; } } ``` An activity is a React component declared with the type `ActivityComponentType`. ```tsx showLineNumbers filename="MyActivity.tsx" copy import type { ActivityComponentType } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; const MyActivity: ActivityComponentType<"MyActivity"> = () => { return (
My Activity
); }; export default MyActivity; ``` `ActivityComponentType` is compatible with `React.ComponentType`. Therefore, you can continue to use `React.FC`, `React.Component`, etc., as you have been. **Stackflow** does not provide a default UI. Instead, it offers basic iOS (`cupertino`) and Android (`android`) UIs through the `@stackflow/plugin-basic-ui`. Register the activity in `stackflow.config.ts` and inject the component in `stackflow()`: ```tsx showLineNumbers filename="stackflow.config.ts" copy import { defineConfig } from "@stackflow/config"; export const config = defineConfig({ activities: [ { name: "MyActivity", }, ], transitionDuration: 350, }); ``` ```tsx showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; import { config } from "./stackflow.config"; import MyActivity from "./MyActivity"; export const { Stack } = stackflow({ config, components: { MyActivity, }, plugins: [ basicRendererPlugin(), basicUIPlugin({ theme: "cupertino", }), ], }); ``` ## Registering initial Activity Have you successfully registered the activity? However, the `` component that you initialized earlier might not be rendering anything. This is because you haven't set an initial activity. Add the `initialActivity` option to `defineConfig()` as follows. ```ts showLineNumbers {9} filename="stackflow.config.ts" copy import { defineConfig } from "@stackflow/config"; export const config = defineConfig({ activities: [ { name: "MyActivity", }, ], initialActivity: () => "MyActivity", transitionDuration: 350, }); ``` If you have successfully registered the initial activity, you can see the rendered result on the screen. Have you experienced the auto-completion of the `MyActivity` value in TypeScript? **Stackflow** will help improve your development productivity through such auto-completion experiences. ## Registering Activity with Parameters Some activities require specific parameters when used. Declare the parameter types using module augmentation and use them in the component: ```tsx showLineNumbers filename="Article.tsx" copy import type { ActivityComponentType } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; declare module "@stackflow/config" { interface Register { Article: { title: string; }; } } const Article: ActivityComponentType<"Article"> = ({ params }) => { return (

{params.title}

); }; export default Article; ``` **Caution** - If the required parameters are not passed from the previous screen, a critical error may occur. **Warning** - Initial activity must not require parameters. --- Have you successfully registered the activity? Now, let's learn how to open the registered activity and navigate between them. # Getting State import { TransitioningStackDiagram } from "../../../components/diagrams/TransitioningStackDiagram"; import { APITable } from "../../../components/APITable"; # Getting State The internal state of **Stackflow** can be described in one word: a **stack** data structure with **transition states**. The activities accessible through the `activities` field contain information related to their basic existence, such as ID, name, and the current transition state. These state values are utilized in various ways to create the `@stackflow/plugin-basic-ui`. (You can create one too!) ## Utilizing Stack State in Rendering To access the stack state in a UI component, use the `useStack()` hook. ```tsx showLineNumbers filename="MyActivity.tsx" copy import { useEffect } from "react"; import { useStack, useFlow, type ActivityComponentType } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; declare module "@stackflow/config" { interface Register { MyActivity: { // activity has no parameters }; Article: { title: string; }; } } const MyActivity: ActivityComponentType<"MyActivity"> = () => { const stack = useStack(); const { replace } = useFlow(); const onClick = () => { replace("Article", { title: "Hello", }); }; useEffect(() => { console.log("Stacked Activities:", stack.activities); console.log("Current Transition State:", stack.globalTransitionState); console.log( "Initial Transition Duration Options", stack.transitionDuration, ); }, [stack]); return (
My Activity
); }; export default MyActivity; ``` There are the following fields in the stack state. | | | | | --------------------- | ----------------- | ----------------------------------------------- | | activities | `Activity[]` | list of activites | | transitionDuration | `number` | `transitionDuration` value set in `stackflow()` | | globalTransitionState | `idle`, `loading` | if current activity is animating or not | ## Utilizing Activity State in Rendering You can use the `useActivity()` hook to get information about the current activity. ```tsx showLineNumbers filename="MyActivity.tsx" copy import { useEffect } from "react"; import { useActivity, useFlow, type ActivityComponentType } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; declare module "@stackflow/config" { interface Register { MyActivity: { // activity has no parameters }; Article: { title: string; }; } } const MyActivity: ActivityComponentType<"MyActivity"> = () => { const activity = useActivity(); const { replace } = useFlow(); const onClick = () => { replace("Article", { title: "Hello", }); }; useEffect(() => { console.log("Transition State of Current Activity:", activity.transitionState); }, [activity]); return (
My Activity
); }; export default MyActivity; ``` The fields in the activity state are as follows. | | | | | ------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------- | | id | `string` | Activity ID | | name | `string` | Registered activity name | | transitionState | `enter-active`, `enter-done`, `exit-active`, `exit-done` | Transition state of the activity | | params | `Object` | Parameters passed to the activity | | isActive | `boolean` | Whether is active activity (`false` when `transitionState` is `exit-active`) | | isTop | `boolean` | Whether is top activity (`true` when `transitionState` is `exit-active`) | | isRoot | `boolean` | Whether is root activity | ## Getting Loader Data If you defined a `loader` for an activity in your config, you can access its data using the `useLoaderData()` hook. ```tsx showLineNumbers filename="HomeActivity.tsx" copy import { useLoaderData, type ActivityComponentType } from "@stackflow/react"; import type { homeActivityLoader } from "./HomeActivity.loader"; const HomeActivity: ActivityComponentType<"HomeActivity"> = () => { const loaderData = useLoaderData(); return (
{/* use loaderData */}
); }; ``` ## Customize UI You can freely customize the UI by using states such as `useActivity()` and `useStack()` in the desired component. If you want to utilize the UI provided by `@stackflow/plugin-basic-ui`, use the provided `AppScreen` component. --- Do you want to extend the UI or logic and share it with other developers? Let's move on to learn how to create a plugin. # import { Steps } from "nextra/components"; # Installation ## Install Stackflow ### Installation Install Stackflow in your React project with the following command. ```sh npm2yarn copy npm install @stackflow/config @stackflow/core @stackflow/react ``` ### Create a Config File Create a `stackflow.config.ts` file and define your activities using `defineConfig()`. ```ts showLineNumbers filename="stackflow.config.ts" copy import { defineConfig } from "@stackflow/config"; export const config = defineConfig({ activities: [ { name: "MyActivity", }, ], transitionDuration: 350, }); ``` ### Initialize Stackflow Create a JavaScript (or TypeScript) file in your project and call the `stackflow()` function, passing the config and activity components. Export `` from the result. Hooks such as `useFlow()` are imported directly from `@stackflow/react` in the components that use them. ```ts showLineNumbers filename="stackflow.ts" copy import { stackflow } from "@stackflow/react"; import { config } from "./stackflow.config"; import MyActivity from "./MyActivity"; export const { Stack } = stackflow({ config, components: { MyActivity, }, plugins: [], }); ``` ## Extend with Basic UI ### Installation **Stackflow** does not include UI (DOM and CSS) implementation by default. To achieve the desired rendering results, you need to add plugins. Install the `@stackflow/plugin-renderer-basic` plugin and the `@stackflow/plugin-basic-ui` extension with the following command. ```sh npm2yarn copy npm install @stackflow/plugin-renderer-basic @stackflow/plugin-basic-ui ``` ### Initialize UI Plugins Initialize the `basicRendererPlugin()` from `@stackflow/plugin-renderer-basic` and the `basicUIPlugin()` from `@stackflow/plugin-basic-ui` in the `plugins` field of the `stackflow()` function as follows. ```ts showLineNumbers filename="stackflow.ts" copy {9-14} import { stackflow } from "@stackflow/react"; import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic"; import { basicUIPlugin } from "@stackflow/plugin-basic-ui"; import { config } from "./stackflow.config"; import MyActivity from "./MyActivity"; export const { Stack } = stackflow({ config, components: { MyActivity, }, plugins: [ basicRendererPlugin(), basicUIPlugin({ theme: "cupertino", }), ], }); ``` ### Include CSS Also, include the CSS provided by `@stackflow/plugin-basic-ui` somewhere in your code. ```ts copy import "@stackflow/plugin-basic-ui/index.css"; ``` ### Render the Stack Component And initialize the `` component at the desired rendering location as follows. ```tsx showLineNumbers filename="App.tsx" copy import { Stack } from "./stackflow"; const App = () => { return (
); }; export default App; ```
--- If you have completed up to this point, let's move on to learn how to register activities. # Introduction import { Callout } from "nextra-theme-docs"; import { Demo } from "../../../components/Demo"; import { CurrentDate } from "../../../components/CurrentDate"; # Introduction **Stackflow** is a project that implements Stack Navigation UX, commonly used in mobile devices (iOS/Android, etc.), in a JavaScript environment, making it easier to develop hybrid apps and webviews. - Stacks screens and maintains scroll position. - Supports transition effects for stacking screens and disappearing screens when navigating back. - Supports iOS-style swipe-back gestures for navigating back. - Passes necessary parameters to the transitioning screen. ### Customization You can use the stack and transition state without any UI, allowing you to customize the UI as you like. You can inject desired extensions into the lifecycle through the plugin interface. ### Integration with Various Platforms The core logic and integration layer are separated, allowing integration with various frontend frameworks. You can inject render logic and UI externally, enabling simultaneous development of mobile webviews and desktop applications from a single codebase. ### Server-Side Rendering, TypeScript Supports `ReactDOMServer.renderToString`. stackflow provides type definitions for all functions. As of , it supports React and React DOM as references. # Navigating Activities import { Callout } from "nextra-theme-docs"; import { APITable } from "../../../components/APITable"; # Navigating Activities If you have successfully registered an activity, it's time to navigate between activities. **Stackflow** supports stacking, replacing, and deleting activities through `useFlow()`. Let's take a look! ## Stacking a New Activity Import `useFlow` directly from `@stackflow/react`. Through the `push()` function within this hook, we can stack a new activity as follows. ```tsx showLineNumbers filename="MyActivity.tsx" copy /push/ import type { ActivityComponentType } from "@stackflow/react"; import { useFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; declare module "@stackflow/config" { interface Register { MyActivity: { // activity has no parameters }; Article: { title: string; }; } } const MyActivity: ActivityComponentType<"MyActivity"> = () => { const { push } = useFlow(); const onClick = () => { push("Article", { title: "Hello", }); }; return (
My Activity
); }; export default MyActivity; ``` `push()` takes the name of the activity to navigate to as the first parameter, the parameters for the activity as the second parameter, and additional options as the third parameter. The third parameter, additional options, is optional and can be omitted (default values will be used). ```ts push("activity_name", { /* activity parameters */ }); // or push( "activity_name", { /* activity parameters */ }, { /* additional options */ }, ); ``` The third parameter of the `push()` function, additional options, includes the following values. | | | | | | ------- | --------- | ------------------------ | ---- | | animate | `boolean` | Turn on or off animation | true | By utilizing TypeScript, you can ensure that activity names and parameters are strictly typed. Use TypeScript to safely and conveniently leverage **Stackflow**. ## Replacing the Current Activity Next, let's look at how to replace the current activity without adding a new activity to the stack. Using the `replace()` function from the `useFlow()` hook, you can replace the current activity as follows. ```tsx showLineNumbers filename="MyActivity.tsx" copy /replace/ import type { ActivityComponentType } from "@stackflow/react"; import { useFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; declare module "@stackflow/config" { interface Register { MyActivity: { // activity has no parameters }; Article: { title: string; }; } } const MyActivity: ActivityComponentType<"MyActivity"> = () => { const { replace } = useFlow(); const onClick = () => { replace("Article", { title: "Hello", }); }; return (
My Activity
); }; export default MyActivity; ``` `replace()` has a similar API to `push()`. It takes the name of the activity to navigate to as the first parameter, the parameters for the activity as the second parameter, and additional options as the third parameter. The third parameter, additional options, is optional and can be omitted (default values will be used). ```ts replace("activity_name", { /* activity parameters */ }); // or replace( "activity_name", { /* activity parameters */ }, { /* additional options */ }, ); ``` The third parameter of the `replace()` function, additional options, includes the following values. | | | | | | ------- | --------- | ------------------------ | ---- | | animate | `boolean` | Turn on or off animation | true | ## Deleting the Current Activity Finally, let's look at how to delete the current activity and return to the previous activity. Using the `pop()` function from the `useFlow()` hook, you can delete the current activity as follows. ```tsx showLineNumbers filename="Article.tsx" copy /pop/ import type { ActivityComponentType } from "@stackflow/react"; import { useFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; declare module "@stackflow/config" { interface Register { Article: { title: string; }; } } const Article: ActivityComponentType<"Article"> = ({ params }) => { const { pop } = useFlow(); const goBack = () => { // Pop a single activity pop(); }; const goBackMultiple = () => { // Pop multiple activities pop(3); }; return (

{params.title}

); }; export default Article; ``` `pop()` takes optional parameters for the number of activities to pop and additional options. These parameters can be omitted, and default values will be used. ```ts pop(); // pop a single activity pop(3); // pop multiple activities pop({ /* additional option */ }); // pop a single activity with additional options pop(3, { /* additional option */ }); // pop multiple activities with additional options ``` The first parameter of the pop() function can specify the number of activities to pop or define additional options. If the first parameter is used for the number of activities, the second parameter can then be used to provide additional options. The additional options include the following values. | | | | | | ------- | --------- | ------------------------ | ---- | | animate | `boolean` | Turn on or off animation | true | --- We have learned how to stack, replace, and delete activities. Now, let's learn how to create a virtual stack within an activity. # Navigating Step import { StepDiagram } from "../../../components/diagrams/StepDiagram"; import { Callout, Link } from "nextra-theme-docs"; # Navigating Step You can use steps when you want to have a virtual stack state within a single activity. Steps work by changing the parameters of the activity. `@stackflow/plugin-history-sync` supports steps. If you need to handle specific state manipulations along with Android back button support on mobile, using the step feature is better than `history.pushState()`. ## Stacking a New Step Import `useStepFlow` directly from `@stackflow/react`. Through the `pushStep()` function within this hook, you can stack a new step as follows. ```tsx showLineNumbers filename="Article.tsx" copy /pushStep/ import type { ActivityComponentType } from "@stackflow/react"; import { useStepFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; declare module "@stackflow/config" { interface Register { Article: { title: string; }; } } const Article: ActivityComponentType<"Article"> = ({ params }) => { // For type safety, put the name of the current activity const { pushStep } = useStepFlow("Article"); const onNextClick = () => { // When you call `pushStep()`, `params.title` changes. pushStep({ title: "Next Title", }); }; return (

{params.title}

); }; export default Article; ``` ## Replacing a Step You can replace the current step using the `replaceStep()` function in `useStepFlow()`. ```tsx showLineNumbers filename="Article.tsx" copy /replaceStep/ import type { ActivityComponentType } from "@stackflow/react"; import { useStepFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; declare module "@stackflow/config" { interface Register { Article: { title: string; }; } } const Article: ActivityComponentType<"Article"> = ({ params }) => { // For type safety, put the name of the current activity const { replaceStep } = useStepFlow("Article"); const onChangeClick = () => { // When you call `replaceStep()`, the title changes to "Next Title". replaceStep({ title: "Next Title", }); }; return (

{params.title}

); }; export default Article; ``` ## Deleting a Step You can delete the current step using the `popStep()` function in `useStepFlow()`. ```tsx showLineNumbers filename="Article.tsx" copy /popStep/ import type { ActivityComponentType } from "@stackflow/react"; import { useStepFlow } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; declare module "@stackflow/config" { interface Register { Article: { title: string; }; } } const Article: ActivityComponentType<"Article"> = ({ params }) => { // For type safety, put the name of the current activity const { popStep } = useStepFlow("Article"); const onPrevClick = () => { // When you call `popStep()`, the current step is deleted. popStep(); }; return (

{params.title}

); }; export default Article; ``` If there's no step to delete, nothing happens when you call `popStep()`. If you use `useFlow().pop()` in a state where multiple steps have been pushed, all the steps stacked inside the activity are deleted at once. --- You've learned the basics of using **Stackflow**. Now, let's go beyond using it and learn about the internal structure of the stack state and specific application methods using it. # # Migration Guide: v1 β†’ v2 This guide covers the breaking changes in Stackflow 2.0 and how to migrate from v1. ## Overview Stackflow 2.0 introduces a config-first approach that separates activity declarations from React components. This enables better performance through framework-agnostic loading and improved type safety. ## Step 1: Install `@stackflow/config` ```sh npm2yarn copy npm install @stackflow/config ``` ## Step 2: Create `stackflow.config.ts` Extract your activity declarations into a config file. **Before:** ```ts filename="stackflow.ts" import { stackflow } from "@stackflow/react"; export const { Stack, useFlow } = stackflow({ transitionDuration: 350, activities: { HomeActivity, MyProfileActivity, }, plugins: [], }); ``` **After:** ```ts filename="stackflow.config.ts" import { defineConfig } from "@stackflow/config"; export const config = defineConfig({ activities: [ { name: "HomeActivity" }, { name: "MyProfileActivity" }, ], transitionDuration: 350, }); ``` ```ts filename="stackflow.ts" import { stackflow } from "@stackflow/react"; import { config } from "./stackflow.config"; export const { Stack } = stackflow({ config, components: { HomeActivity, MyProfileActivity, }, plugins: [], }); ``` ## Step 3: Update `historySyncPlugin` Routes are now declared in `stackflow.config.ts` instead of the plugin options. **Before:** ```ts historySyncPlugin({ routes: { HomeActivity: "/", MyProfileActivity: "/my-profile", }, fallbackActivity: () => "HomeActivity", }) ``` **After:** In `stackflow.config.ts`: ```ts defineConfig({ activities: [ { name: "HomeActivity", route: "/" }, { name: "MyProfileActivity", route: "/my-profile" }, ], }) ``` In `stackflow.ts`: ```ts historySyncPlugin({ config, fallbackActivity: () => "HomeActivity", }) ``` ## Step 4: Update Activity Types Types are now registered via module augmentation instead of component Props. **Before:** ```ts filename="Article.tsx" import type { ActivityComponentType } from "@stackflow/react"; type ArticleParams = { title: string; }; const Article: ActivityComponentType = ({ params }) => { // ... }; ``` **After:** ```ts filename="Article.tsx" import type { ActivityComponentType } from "@stackflow/react"; declare module "@stackflow/config" { interface Register { Article: { title: string; }; } } const Article: ActivityComponentType<"Article"> = ({ params }) => { // params.title is typed }; ``` ## Step 5: Update `useFlow` and `useStepFlow` Imports Hooks are now imported directly from `@stackflow/react` instead of being created from a factory function. **Before:** ```ts filename="stackflow.ts" import { stackflow } from "@stackflow/react"; export const { Stack, useFlow } = stackflow({ transitionDuration: 350, activities: { HomeActivity, MyProfileActivity, }, plugins: [], }); ``` ```ts filename="HomeActivity.tsx" import { useFlow } from "./stackflow"; // from the stackflow() factory ``` **After:** ```ts import { useFlow } from "@stackflow/react"; // direct import ``` References to the old `useActions()` helper should also be updated to `useFlow()` imported directly from `@stackflow/react`. ## Step 6: Rename Step Navigation Methods The step navigation function names have changed. | Before | After | |--------|-------| | `stepPush()` | `pushStep()` | | `stepReplace()` | `replaceStep()` | | `stepPop()` | `popStep()` | ## Step 7: Update `` Import The `` component is now imported directly. **Before:** ```ts filename="Link.ts" import { createLinkComponent } from "@stackflow/link"; import type { TypeActivities } from "./stackflow"; export const { Link } = createLinkComponent(); ``` **After:** ```ts import { Link } from "@stackflow/link"; ``` ## Step 8: Update Import Paths Replace all occurrences of the old entry points. | Before | After | |--------|-------| | `@stackflow/react/future` | `@stackflow/react` | | `@stackflow/link/future` | `@stackflow/link` | ## Removed Packages The following packages have been removed in v2: - `@stackflow/plugin-preload` β€” Use the built-in Loader API instead. See [Loader API](/docs/advanced/preloading). - `@stackflow/plugin-map-initial-activity` β€” Use `initialActivity` in `defineConfig()` instead. ## Removed Hooks The following hooks are no longer exported from `@stackflow/react`: | v1 hook | v2 replacement | |---------|----------------| | `useActiveEffect(effect)` | Use React's `useEffect` with `useActivity().isActive`. For external side-effects that should run immediately on focus transitions, use `useFocusEffect()` from `@stackflow/plugin-lifecycle`. | | `useEnterDoneEffect(effect, deps)` | Use React's `useEffect` with `useActivity().isTop` and `transitionState === "enter-done"`. | | `useStep()` | Use `useActivity()` and derive the latest non-root step from `activity.steps` (`activity.steps.filter((step) => step.id !== activity.id).at(-1) ?? null`). If you only need current step params, use the activity/component `params`. | ## API Correspondence Table | v1 | v2 | |----|-----| | `stackflow({ transitionDuration, activities, plugins })` | `stackflow({ config, components, plugins })` | | `ActivityComponentType` | `ActivityComponentType<"ActivityName">` | | `useActions()` | `useFlow()` from `@stackflow/react` | | `useFlow()` from the `stackflow()` factory | `useFlow()` from `@stackflow/react` | | `useStepFlow()` from the `stackflow()` factory | `useStepFlow()` from `@stackflow/react` | | `stepPush/stepReplace/stepPop` | `pushStep/replaceStep/popStep` | | `createLinkComponent()` | `import { Link } from "@stackflow/link"` | | `historySyncPlugin({ routes, fallbackActivity })` | `historySyncPlugin({ config, fallbackActivity })` | | `preloadPlugin(...)` | Built-in Loader API | # Stackflow – The Simplest Stack Navigation for JavaScript and TypeScript import { IndexPage } from "../components/index-page";