Skip to content

changeRoute does not receive historyState when atPath is called with state #305

Description

@pablogm95

Bug

When using atPath(path, state) with changeRoute configured via configure(), the state is never forwarded to the changeRoute callback. As a result, location.state is always undefined in the rendered component, even though state was passed to atPath.

Root cause

In src/wrap.tsx, setupRoute accepts historyState as a parameter but the changeRoute type only declares path and the call only forwards the path:

// line 140 — type does not include state
const setupRoute = (
  hasPath: boolean,
  path: string,
  historyState: object | undefined,
  history: BrowserHistory | undefined,
  changeRoute: (path: string) => void,   // ← state missing from type
) => {
  ...
  history.push(path, historyState)  // ✅ legacy: forwards both
  ...
  changeRoute(path)                 // ❌ modern: drops historyState
}

The historyState travels correctly through atPath → updateOptions → getOptions → getMount → setupRoute, but is silently dropped at the changeRoute(path) call on line 155.

The existing test for state (tests/lib/atPath.test.ts ~line 75) passes because it uses the legacy configure({ history }) path — not changeRoute.

Proposed fix

Pass historyState as a second argument to changeRoute, and update the type accordingly:

// src/wrap.tsx
const setupRoute = (
  hasPath: boolean,
  path: string,
  historyState: object | undefined,
  history: BrowserHistory | undefined,
  changeRoute: (path: string, state?: object) => void,  // add state param
) => {
  ...
  changeRoute(path, historyState)  // forward state
}

Callers can then opt-in by declaring the second parameter:

configure({
  changeRoute: (route, state) => history.push(route, state),
})

This is non-breaking: existing changeRoute implementations that only declare one parameter will ignore the second argument.

Steps to reproduce

configure({
  changeRoute: (route) => history.push(route),
})

wrap(App)
  .atPath('/some-path', { myKey: true })
  .mount()

// Inside component: useLocation().state === null
// Expected: useLocation().state === { myKey: true }

Workaround

Use history.push directly after mounting:

wrap(App).atPath('/').withNetwork(responses).mount()
await screen.findByText('some content')
history.push('/some-path', { myKey: true })

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions