Skip to content

feat(react,vue,solid): add closed and loading to route views - #106

Draft
sergeysova wants to merge 12 commits into
mainfrom
claude/createroute-otherwise-loading-h1g5nw
Draft

feat(react,vue,solid): add closed and loading to route views#106
sergeysova wants to merge 12 commits into
mainfrom
claude/createroute-otherwise-loading-h1g5nw

Conversation

@sergeysova

@sergeysova sergeysova commented Jul 29, 2026

Copy link
Copy Markdown
Member

Summary

Brings back the atomic-router skeleton pattern (nested CreateRouteView with otherwise) in the shape this router uses — as two new properties of createRouteView / createLazyRouteView in the React, Vue, and Solid bindings:

  • closed — rendered while the route is not opened;
  • loading — rendered while the route is pending (beforeOpen effects, a chainRoute preparation) and while a lazy chunk loads.

A view now reads as its three states: view when opened, loading while pending, closed while closed. createRoutesView({ otherwise }) keeps its name and its different meaning — nothing matched the URL.

createRoutesView and Outlet still render a single view. Resolution order:

  1. the deepest opened view (unchanged behaviour, including parent suppression and last-declared-sibling priority);
  2. otherwise the loading of a pending view;
  3. otherwise the closed of a closed view;
  4. otherwise the otherwise prop of createRoutesView (null inside an Outlet).

Because an opened view always wins, loading never replaces a page already on screen. Fallbacks are wrapped by the view's layout and by its withLayout group, so a grouped layout stays mounted while a nested chain resolves — the parent view keeps rendering while its Outlet shows the child's skeleton.

This also removes the flash of the routes-view otherwise (the not-found screen) that appeared for a split second while a lazy view's chunk or chained data was loading: createLazyRouteView's fallback only covered the chunk request, so a pending route fell through to the not-found screen. fallback is deprecated in favour of loading, which covers both waits, and keeps working as its alias.

Each binding gained a mirrored internal resolve-route-view module that owns the fallback wrapping and the resolution; createRoutesView and Outlet now render through it. useOpenedViews keeps its current public semantics (opened views only).

React Native re-exports the React factories, so the props type-check there; the @react-navigation navigators mount every route view as a screen and render view only — documented in the React Native reference.

Type of change

  • ✨ Feature
  • 📝 Documentation

Checklist

  • I linked a related issue (or explained why there isn't one) — no issue; requested directly in chat
  • pnpm build passes
  • pnpm typecheck passes (plus pnpm verify:typecheck-standalone, pnpm :docs typecheck, pnpm :docs check-links)
  • pnpm test passes and I added/updated tests for my change (new specs across the three bindings; Solid example e2e also run against a local Chromium)
  • pnpm lint passes
  • I added a changeset (minor for @effector/router-react, @effector/router-vue, @effector/router-solid)

@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@changeset-bot

changeset-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e40e675

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@effector/router-react Minor
@effector/router-solid Minor
@effector/router-vue Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@netlify

netlify Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploy Preview for effector-router ready!

Name Link
🔨 Latest commit e40e675
🔍 Latest deploy log https://app.netlify.com/projects/effector-router/deploys/6a6a35abd35eb50008023a0e
😎 Deploy Preview https://deploy-preview-106--effector-router.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Adds failing specs for the upcoming `otherwise` and `loading` properties of
`createRouteView`/`createLazyRouteView` in every web binding: a closed route
renders `otherwise`, a pending route renders `loading`, an opened sibling still
wins over any declared fallback, fallbacks render through `Outlet`, they are
wrapped by the view layout (including `withLayout` groups), and `loading`
doubles as the lazy chunk fallback.
`createRouteView`/`createLazyRouteView` accept two new components:

- `otherwise` — rendered while the route is not opened;
- `loading` — rendered while the route is pending, and used as the lazy chunk
  fallback unless the chunk-only `fallback` is declared.

Both take part in the routes view / `Outlet` selection: an opened view always
wins, and only when nothing is opened the last declared fallback renders, with a
pending `loading` taking precedence over a closed `otherwise`. Fallbacks are
wrapped by the same layout the view uses, including `withLayout` groups, so a
grouped layout stays mounted while a nested chain resolves — the skeleton
pattern that `atomic-router`'s nested route views used to cover.

Each binding gained a mirrored internal `resolve-route-view` module owning the
fallback wrapping and the resolution; `createRoutesView` and `Outlet` now render
through it.
Covers the new properties in the React, Vue, and Solid reference pages for
createRouteView/createLazyRouteView — including the selection order used by
createRoutesView and Outlet, the layout wrapping, and the caveat about a
per-view otherwise shadowing the routes-view not-found screen.

Also notes the pending-state skeleton in the chainRoute reference, records that
native navigators render `view` only, and adds the mirrored resolve-route-view
module to AGENTS.md.
@sergeysova
sergeysova force-pushed the claude/createroute-otherwise-loading-h1g5nw branch from 8b8e72d to f816d2b Compare July 29, 2026 11:29
Locks the contract that removes the routes-view `otherwise` flash: the lazy
`fallback` component also covers the pending route, not just the chunk request,
and `loading` wins when both are declared.
`fallback` only covered the chunk request, so a lazy view whose route was still
pending fell through to the routes view `otherwise` — the not-found screen
flashed for a moment before the page appeared.

`loading` covers both waits, and `fallback` now behaves as its alias: it is used
when `loading` is absent, and it also renders while the route is pending. The
type is marked `@deprecated` so editors point at `loading`.
Rewrites the lazy route view examples and property tables around `loading`,
documents `fallback` as its deprecated alias, and notes that covering both waits
with one component is what stops the routes view `otherwise` from flashing.
…osed

`closed` is the exact complement of `opened` and matches the router's own
lifecycle vocabulary, so a view now reads as its three states: `view` when
opened, `loading` while pending, `closed` while closed.

It also removes the clash with `createRoutesView({ otherwise })`, which keeps
its name and its different meaning — nothing matched the URL. That prop is
untouched.
@sergeysova sergeysova changed the title feat(react,vue,solid): add otherwise and loading to route views feat(react,vue,solid): add closed and loading to route views Jul 29, 2026
Answers the question this feature came from: how to keep a skeleton per nesting
level while a chain of routes prepares, instead of letting the not-found screen
flash between navigations.

The guide is task-oriented, so it lives in the how-to quadrant, which until now
was an empty placeholder — it is now listed in the sidebar and the top nav.
Every snippet is one runnable file, docs/how-to/nested-loading-skeletons.tsx,
executed by packages/react/tests/how-to-nested-loading.test.tsx: the test drives
three deferred effects and asserts what is on screen at each stage, so the
documented result is the tested one.
The resolved selection was a fresh object on every router update, so any change
in the pending flags — a sibling chain starting to prepare — re-rendered the
selected view and everything under it.

The resolution now keeps its identity while the same view and component stay
selected, and both renderers sit behind a memo boundary, so unrelated updates
stop there. The outlet context value is memoized per view as well. Vue and Solid
already behaved this way; all three suites now assert it.
Collects the shapes the same pieces take for adjacent goals: filling an empty
slot, keeping the frame while only the inner part swaps, avoiding the remount a
component swap costs, streaming a ready child inside a parent skeleton, holding
a transition before the URL commits so the current page stays, redirecting
instead, splitting a level into a chunk, and grouping a layout.

The two claims that are not obvious from the API — an `<Outlet />` inside
`loading`, and `beforeNavigate` keeping the previous page on screen — are
covered by tests next to the guide's own.
Applies the review suggestion: the table already shows the sequence, and the
mechanism section states the not-found behaviour where it belongs.
Two guarantees the suite did not hold before, both found while investigating
layout remounts:

- switching pages inside one `withLayout` group keeps the layout instance, and
  moving to another group swaps it exactly once;
- the frame between a closing route and the opening one belongs to the target's
  `loading`, so the not-found screen never appears mid-navigation.

Both depend on the target declaring `loading`: that frame has nothing opened,
and without a fallback the routes view falls through to `otherwise` and takes
the layout down with it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant