← All reusables
setupStorybookUrl
utilityInstallation
npx jsrepo add github/reatom/reusables setupStorybookUrlCopy the source code below and save it to the specified file path in your project.
setup-storybook-url.ts
import { context, noop, urlAtom, withChangeHook } from '@reatom/core'
const originalHref = window.location.href
/**
* Configures `urlAtom` for Storybook: disables History API sync and restores
* the original iframe URL after every state change.
*
* Call once per story in a Storybook decorator. Returns a context frame
* suitable for passing to `reatomContext.Provider`.
*
* @param initialPath - Optional path to navigate to after setup
* @param beforeNavigate - Optional setup (e.g. auth state) executed inside the
* frame before navigation, so route matching and loader evaluation happen
* only once with the correct state, avoiding concurrent loader abort errors
* @returns A context frame to provide to React context
* @see https://dev.to/guria/reatom-extensibility-saves-the-day-595e
*/
export const setupStorybookUrl = (
initialPath = '',
beforeNavigate?: () => void,
) => {
const frame = context.start()
frame.run(() => {
urlAtom.sync.set(() => noop)
urlAtom.extend(
withChangeHook(() => {
window.history.replaceState({}, '', originalHref)
}),
)
beforeNavigate?.()
const base = import.meta.env.BASE_URL ?? ''
urlAtom.go(base + initialPath)
})
return frame
}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
- Captures the current
window.location.href(module-level) - Creates an isolated context frame via
context.start() - Inside the frame:
- Replaces
urlAtom.syncwithnoopto prevent History API calls - Adds a change hook that restores the original URL via
history.replaceStateafter everyurlAtomupdate - Runs
beforeNavigate(if provided) — state set here is visible to route loaders on first evaluation, avoiding concurrent loader abort errors - Navigates to
BASE_URL + initialPathviaurlAtom.go()
- Replaces
- 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>
)
}