From 3b071429016c0a2532161f158841f07dcf62ffe5 Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Wed, 16 Nov 2022 09:00:11 -0700 Subject: [PATCH] fix: hydration, pending timing removal --- README.md | 1 - docs/comparison.md | 91 +++++++++---------- docs/config.json | 4 - docs/guide/pending-states.md | 5 - docs/overview.md | 3 +- examples/react/basic-ssr-lite/server.js | 14 +-- .../react/basic-ssr-lite/src/entry-server.tsx | 68 ++++++++++---- .../react/kitchen-sink-codesplit/src/main.tsx | 10 -- .../kitchen-sink-codesplit/src/router.tsx | 7 ++ .../react/kitchen-sink-routegen/src/main.tsx | 10 -- .../kitchen-sink-routegen/src/router.tsx | 7 ++ examples/react/kitchen-sink/src/main.tsx | 14 +-- examples/react/with-trpc/client/main.tsx | 15 ++- packages/react-router/src/index.tsx | 6 +- packages/router-core/src/router.ts | 36 +++++--- 15 files changed, 147 insertions(+), 144 deletions(-) delete mode 100644 docs/guide/pending-states.md diff --git a/README.md b/README.md index dd3c069eac..9b4c8d4880 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,6 @@ Enjoy this library? Try the entire [TanStack](https://tanstack.com)! [React Quer - Prefetching - Automatic Prefetching - Transitions -- Pending States - Error Boundaries - Code Splitting - Layout Routes diff --git a/docs/comparison.md b/docs/comparison.md index 75b746de27..1962cfacf9 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -15,52 +15,51 @@ Feature/Capability Key: - 🔶 Possible, but requires custom code/implementation/casting - 🛑 Not officially supported -| | TanStack Router | React Router DOM [_(Website)_][react-router] | -| ---------------------------------------------- | ---------------------------------------------- | ----------------------------------------------------- | -| Github Repo / Stars | [![][stars-tanstack-router]][gh-tanstack-router] | [![][stars-react-router]][gh-react-router] | -| Bundle Size | [![][bp-tanstack-router]][bpl-tanstack-router] | [![][bp-react-router]][bpl-react-router] | -| History, Memory & Hash Routers | ✅ | ✅ | -| Nested / Layout Routes | ✅ | ✅ | -| Suspense-like Route Transitions | ✅ | ✅ | -| Typesafe Route Configurations | ✅ | 🛑 | -| Loaders | ✅ | ✅ | -| Typesafe Loaders | ✅ | 🔶 | -| Loader Caching (SWR + Invalidation) | ✅ | 🛑 | -| Actions | ✅ | ✅ | -| Typesafe Actions | ✅ | 🔶 | -| Route Prefetching | ✅ | ✅ | -| Auto Route Prefetching | ✅ | 🛑 | -| Route Prefetching Delay | ✅ | 🔶 | -| Path Params | ✅ | ✅ | -| Typesafe Path Params | ✅ | 🛑 | -| Path Param Validation | ✅ | 🛑 | -| Custom Path Param Parsing/Serialization | ✅ | 🛑 | -| Code-Splitting | ✅ | ✅ | -| Ranked Routes | 🟢 | ✅ | -| Active Link Customization | ✅ | ✅ | -| Ephemeral Optimistic UI | ✅ | ✅ | -| Typesafe Absolute + Relative Navigation | ✅ | 🛑 | -| Route Mount/Transition/Unmount Events | ✅ | 🛑 | -| Official Devtools | 🟢 | 🛑 | -| Basic Search Params | ✅ | ✅ | -| Search Param Hooks | ✅ | ✅ | -| ``/`useNavigate` Search Param API | ✅ | 🟡 (search-string only via the `to`/`search` options) | -| JSON Search Params | ✅ | 🔶 | -| TypeSafe Search Params | ✅ | 🛑 | -| Search Param Schema Validation | ✅ | 🛑 | -| Search Param Immutability + Structural Sharing | ✅ | 🛑 | -| Custom Search Param parsing/serialization | ✅ | 🔶 | -| Hierarchical Search Param Transforms | ✅ | 🛑 | -| Async Route Elements | ✅ | 🛑 | -| Suspense Route Elements | ✅ | ✅ | -| Route Error Elements | ✅ | ✅ | -| Route Pending Elements | ✅ | 🛑 | -| Pending Timing (delay, min-show) | ✅ | 🛑 | -| ``/`usePrompt` | ✅ | 🔶 | -| SSR | 🛑 (Coming Soon) | ✅ | -| Navigation Scroll Restoration | 🛑 (Coming Soon) | ✅ | -| Deferred Loader Streaming | 🛑 (Coming Soon) | ✅ | -| `
` API | 🛑 | ✅ | +| | TanStack Router | React Router DOM [_(Website)_][react-router] | +| ---------------------------------------------- | ------------------------------------------------ | ----------------------------------------------------- | +| Github Repo / Stars | [![][stars-tanstack-router]][gh-tanstack-router] | [![][stars-react-router]][gh-react-router] | +| Bundle Size | [![][bp-tanstack-router]][bpl-tanstack-router] | [![][bp-react-router]][bpl-react-router] | +| History, Memory & Hash Routers | ✅ | ✅ | +| Nested / Layout Routes | ✅ | ✅ | +| Suspense-like Route Transitions | ✅ | ✅ | +| Typesafe Route Configurations | ✅ | 🛑 | +| Loaders | ✅ | ✅ | +| Typesafe Loaders | ✅ | 🔶 | +| Loader Caching (SWR + Invalidation) | ✅ | 🛑 | +| Actions | ✅ | ✅ | +| Typesafe Actions | ✅ | 🔶 | +| Route Prefetching | ✅ | ✅ | +| Auto Route Prefetching | ✅ | 🛑 | +| Route Prefetching Delay | ✅ | 🔶 | +| Path Params | ✅ | ✅ | +| Typesafe Path Params | ✅ | 🛑 | +| Path Param Validation | ✅ | 🛑 | +| Custom Path Param Parsing/Serialization | ✅ | 🛑 | +| Code-Splitting | ✅ | ✅ | +| Ranked Routes | 🟢 | ✅ | +| Active Link Customization | ✅ | ✅ | +| Ephemeral Optimistic UI | ✅ | ✅ | +| Typesafe Absolute + Relative Navigation | ✅ | 🛑 | +| Route Mount/Transition/Unmount Events | ✅ | 🛑 | +| Official Devtools | 🟢 | 🛑 | +| Basic Search Params | ✅ | ✅ | +| Search Param Hooks | ✅ | ✅ | +| ``/`useNavigate` Search Param API | ✅ | 🟡 (search-string only via the `to`/`search` options) | +| JSON Search Params | ✅ | 🔶 | +| TypeSafe Search Params | ✅ | 🛑 | +| Search Param Schema Validation | ✅ | 🛑 | +| Search Param Immutability + Structural Sharing | ✅ | 🛑 | +| Custom Search Param parsing/serialization | ✅ | 🔶 | +| Hierarchical Search Param Transforms | ✅ | 🛑 | +| Async Route Elements | ✅ | 🛑 | +| Suspense Route Elements | ✅ | ✅ | +| Route Error Elements | ✅ | ✅ | +| Route Pending Elements | ✅ | 🛑 | +| ``/`usePrompt` | ✅ | 🔶 | +| SSR | 🛑 (Coming Soon) | ✅ | +| Navigation Scroll Restoration | 🛑 (Coming Soon) | ✅ | +| Deferred Loader Streaming | 🛑 (Coming Soon) | ✅ | +| `` API | 🛑 | ✅ | [bp-tanstack-router]: https://badgen.net/bundlephobia/minzip/@tanstack/react-router@alpha?label=💾 [bpl-tanstack-router]: https://bundlephobia.com/result?p=@tanstack/react-router@alpha diff --git a/docs/config.json b/docs/config.json index aa390d91be..b2d1f568c3 100644 --- a/docs/config.json +++ b/docs/config.json @@ -118,10 +118,6 @@ "label": "Code-Splitting", "to": "guide/route-elements" }, - { - "label": "Pending States", - "to": "guide/pending-states" - }, { "label": "Prompts", "to": "guide/prompts" diff --git a/docs/guide/pending-states.md b/docs/guide/pending-states.md deleted file mode 100644 index 16b070d204..0000000000 --- a/docs/guide/pending-states.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Pending States ---- - -TODO diff --git a/docs/overview.md b/docs/overview.md index 06629e5aff..3939c35076 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -11,7 +11,7 @@ TanStack Router is a UI router for building applications in React, Preact, Vue, - Stale-while-revalidate Loader Cache - Automatic (and manual) cache invalidation - Automatic route prefetching -- Suspense-like route transitions with optional pending states +- Suspense-like route transitions - Asynchronous route elements and error boundaries - File-Splitting & Code-splitting - Typesafe JSON-first Search Params w/ Immutable Structural Sharing @@ -25,7 +25,6 @@ TanStack Router is a UI router for building applications in React, Preact, Vue, TanStack Router builds on concepts and patterns popularized by many other OSS projects, including: - - [TRPC](https://trpc.io/) - [Remix](https://remix.run) - [Chicane](https://swan-io.github.io/chicane/) diff --git a/examples/react/basic-ssr-lite/server.js b/examples/react/basic-ssr-lite/server.js index d5185d21c1..77a5fc1b52 100644 --- a/examples/react/basic-ssr-lite/server.js +++ b/examples/react/basic-ssr-lite/server.js @@ -78,19 +78,7 @@ export async function createServer( render = (await import('./dist/server/entry-server.tsx')).render } - const context = {} - const [appHead, appHtml] = await render(url, context) - - if (context.url) { - // Somewhere a `` was rendered - return res.redirect(301, context.url) - } - - const html = template - .replace('', appHead) - .replace(``, appHtml) - - res.status(200).set({ 'Content-Type': 'text/html' }).end(html) + render({ template, url, res }) } catch (e) { !isProd && vite.ssrFixStacktrace(e) console.log(e.stack) diff --git a/examples/react/basic-ssr-lite/src/entry-server.tsx b/examples/react/basic-ssr-lite/src/entry-server.tsx index 4627904325..7c51fa8813 100644 --- a/examples/react/basic-ssr-lite/src/entry-server.tsx +++ b/examples/react/basic-ssr-lite/src/entry-server.tsx @@ -4,39 +4,69 @@ import { createMemoryHistory, RouterProvider } from '@tanstack/react-router' import jsesc from 'jsesc' import { App } from './App' import { router } from './router' +import { ServerResponse } from 'http' + +export async function render(opts: { + url: string + template: string + res: ServerResponse +}) { + router.reset() -export async function render(url: string) { const memoryHistory = createMemoryHistory({ - initialEntries: [url], + initialEntries: [opts.url], }) router.update({ history: memoryHistory, }) - const unsub = router.mount() - await router.load() + router.mount()() // and unsubscribe immediately - const routerState = router.dehydrateState() - - const res = [ - ``, - ReactDOMServer.renderToString( - - - , - ), - ] - - unsub() - router.reset() + )})` + + opts.res.write(routerScript) + }) + + const leadingHtml = opts.template.substring( + 0, + opts.template.indexOf(''), + ) + + const tailingHtml = opts.template.substring( + opts.template.indexOf('') + ''.length, + ) + + opts.res.setHeader('Content-Type', 'text/html') + + const stream = ReactDOMServer.renderToPipeableStream( + + + , + { + onShellReady: () => { + opts.res.write(leadingHtml) + stream.pipe(opts.res) + }, + onError: (err) => { + console.log(err) + }, + onAllReady: () => { + opts.res.end(tailingHtml) + }, + }, + ) + + // router.reset() - return res + // return res } diff --git a/examples/react/kitchen-sink-codesplit/src/main.tsx b/examples/react/kitchen-sink-codesplit/src/main.tsx index ffd1b67c29..85f1b127db 100644 --- a/examples/react/kitchen-sink-codesplit/src/main.tsx +++ b/examples/react/kitchen-sink-codesplit/src/main.tsx @@ -21,15 +21,6 @@ function App() { 2000, ) - const PendingComponent = React.useCallback( - () => ( -
- -
- ), - [], - ) - return ( <> {/* More stuff to tweak our sandbox setup in real-time */} @@ -93,7 +84,6 @@ function App() { ( +
+ +
+ ), }) diff --git a/examples/react/kitchen-sink-routegen/src/main.tsx b/examples/react/kitchen-sink-routegen/src/main.tsx index ffd1b67c29..85f1b127db 100644 --- a/examples/react/kitchen-sink-routegen/src/main.tsx +++ b/examples/react/kitchen-sink-routegen/src/main.tsx @@ -21,15 +21,6 @@ function App() { 2000, ) - const PendingComponent = React.useCallback( - () => ( -
- -
- ), - [], - ) - return ( <> {/* More stuff to tweak our sandbox setup in real-time */} @@ -93,7 +84,6 @@ function App() { ( +
+ +
+ ), }) diff --git a/examples/react/kitchen-sink/src/main.tsx b/examples/react/kitchen-sink/src/main.tsx index 1b17ab936a..025c90ef6c 100644 --- a/examples/react/kitchen-sink/src/main.tsx +++ b/examples/react/kitchen-sink/src/main.tsx @@ -164,6 +164,11 @@ const routeConfig = createRouteConfig().createChildren((createRoute) => [ const router = createReactRouter({ routeConfig, + defaultPendingComponent: () => ( +
+ +
+ ), }) // Provide our location and routes to our application @@ -249,7 +254,6 @@ function App() { - - - ) -} - function Root() { const routerState = router.useState() diff --git a/examples/react/with-trpc/client/main.tsx b/examples/react/with-trpc/client/main.tsx index cfbdb8d5d8..733a63b2ba 100644 --- a/examples/react/with-trpc/client/main.tsx +++ b/examples/react/with-trpc/client/main.tsx @@ -73,6 +73,11 @@ const routeConfig = createRouteConfig().createChildren((createRoute) => [ const router = createReactRouter({ routeConfig, + defaultPendingComponent: () => ( +
+ +
+ ), }) // Provide our location and routes to our application @@ -84,15 +89,7 @@ function App() { to start rendering our matches when we're // ready. This also let's us use router API's in before rendering any routes */} - - - - } - defaultPreload="intent" - > + diff --git a/packages/react-router/src/index.tsx b/packages/react-router/src/index.tsx index c7ab0242a4..762384e195 100644 --- a/packages/react-router/src/index.tsx +++ b/packages/react-router/src/index.tsx @@ -445,10 +445,8 @@ export function RouterProvider< router.update(rest) useRouterSubscription(router) - useLayoutEffect(() => { - const unsub = router.mount() - router.load() - return unsub + React.useEffect(() => { + return router.mount() }, [router]) return ( diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index 685268913d..790f81f8fa 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -6,6 +6,7 @@ import { History, MemoryHistory, } from 'history' +import React from 'react' import invariant from 'tiny-invariant' import { GetFrameworkGeneric } from './frameworks' @@ -407,16 +408,25 @@ export function createRouter< return router.routesById[id] }, notify: (): void => { - router.state = { - ...router.state, - isFetching: - router.state.status === 'loading' || - router.state.matches.some((d) => d.isFetching), - isPreloading: Object.values(router.matchCache).some( - (d) => - d.match.isFetching && - !router.state.matches.find((dd) => dd.matchId === d.match.matchId), - ), + const isFetching = + router.state.status === 'loading' || + router.state.matches.some((d) => d.isFetching) + + const isPreloading = Object.values(router.matchCache).some( + (d) => + d.match.isFetching && + !router.state.matches.find((dd) => dd.matchId === d.match.matchId), + ) + + if ( + router.state.isFetching !== isFetching || + router.state.isPreloading !== isPreloading + ) { + router.state = { + ...router.state, + isFetching, + isPreloading, + } } cascadeLoaderData(router.state.matches) @@ -454,7 +464,7 @@ export function createRouter< Object.assign(match, dehydratedMatch) }) - router.loadMatches(matches) + matches.forEach((match) => match.__.validate()) router.state = { ...router.state, @@ -476,7 +486,9 @@ export function createRouter< router.__.commitLocation(next, true) } - // router.load() + if (!router.state.matches.length) { + router.load() + } const unsub = router.history.listen((event) => { router.load(router.__.parseLocation(event.location, router.location))