← All reusables

setupStorybookUrl

utility

Installation

npx jsrepo add github/reatom/reusables setupStorybookUrl

Documentation

setupStorybookUrl

Configures urlAtom for Storybook: disables History API sync and restores the original iframe URL after every state change. Routing state still works internally — components respond to URL changes, links generate correct paths — but the iframe URL stays fixed so Storybook remains happy.

Returns a context frame suitable for passing to reatomContext.Provider, isolating each story's routing state.

setupStorybookUrl(initialPath?, beforeNavigate?)

Call in a Storybook decorator to set up routing per story.

Parameters

Parameter Type Default Description
initialPath string '' Optional path to navigate to after setup
beforeNavigate () => void Optional setup (e.g. auth state) executed inside the frame before navigation, so route matching and loaders run only once with the correct state

Returns

A context frame (Frame) to provide to React context.

Example

// .storybook/preview.tsx
import { reatomContext } from '@reatom/react'
import { useMemo, type PropsWithChildren } from 'react'
import { setupStorybookUrl } from '#reatom/utility/setup-storybook-url'

function ReatomDecorator({
  children,
  initialPath = '',
  authenticated = true,
}: PropsWithChildren<{ authenticated?: boolean; initialPath?: string }>) {
  const frame = useMemo(
    () =>
      setupStorybookUrl(initialPath, () => {
        // Runs before navigation so protected-route loaders see the
        // correct auth state on their first (and only) evaluation.
        authSessionAtom.set(authenticated ? mockSession : null)
      }),
    [authenticated, initialPath],
  )
  return (
    <reatomContext.Provider value={frame}>{children}</reatomContext.Provider>
  )
}

// Use in decorators:
// (Story, { parameters }) => (
//   <ReatomDecorator
//     authenticated={parameters['authenticated']}
//     initialPath={parameters['initialPath']}
//   >
//     <Story />
//   </ReatomDecorator>
// )

How it works

  1. Captures the current window.location.href (module-level)
  2. Creates an isolated context frame via context.start()
  3. Inside the frame:
    • Replaces urlAtom.sync with noop to prevent History API calls
    • Adds a change hook that restores the original URL via history.replaceState after every urlAtom update
    • Runs beforeNavigate (if provided) — state set here is visible to route loaders on first evaluation, avoiding concurrent loader abort errors
    • Navigates to BASE_URL + initialPath via urlAtom.go()
  4. Returns the frame for use with reatomContext.Provider

Reference

Based on the pattern from Reatom Extensibility Saves the Day and real usage in modern-stack.

Example

// .storybook/preview.tsx
import { atom } from '@reatom/core'
import { reatomContext } from '@reatom/react'
import { useMemo, type PropsWithChildren } from 'react'

import { setupStorybookUrl } from './setup-storybook-url'

// Stand-in for your app's session state read by protected-route loaders.
const authSessionAtom = atom<{ userId: string } | null>(null, 'authSessionAtom')
const mockSession = { userId: 'storybook' }

// Use in a Storybook decorator to provide a context frame per story.
// Routing state works internally — components respond to URL changes,
// links generate correct paths — but the iframe URL stays fixed.
function ReatomDecorator({
  children,
  initialPath = '',
  authenticated = true,
}: PropsWithChildren<{ authenticated?: boolean; initialPath?: string }>) {
  const frame = useMemo(
    () =>
      setupStorybookUrl(initialPath, () => {
        // Runs inside the frame before navigation, so route matching and
        // loader evaluation happen only once with the correct auth state.
        authSessionAtom.set(authenticated ? mockSession : null)
      }),
    [authenticated, initialPath],
  )
  return (
    <reatomContext.Provider value={frame}>{children}</reatomContext.Provider>
  )
}