Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
006ab08
fix(router): context issues
tannerlinsley Jul 6, 2024
2786086
remove await (and eslint-disable comment)
schiller-manuel Jul 6, 2024
591d412
copied test from https://github.com/TanStack/router/pull/1891
schiller-manuel Jul 6, 2024
ad4b820
copied test from https://github.com/TanStack/router/pull/1876
schiller-manuel Jul 6, 2024
1e78f0c
fix formatting
schiller-manuel Jul 6, 2024
26e64b1
for the context issues pr (#1908)
SeanCassiere Jul 7, 2024
f0a5f54
fix(router): it's now safe to preload an active route; isLoading is a…
tannerlinsley Jul 7, 2024
febc86e
Merge branch 'main' into fix-context-issues
lachlancollins Jul 7, 2024
18c4429
do not export `getRouteMatch`
schiller-manuel Jul 7, 2024
e43c69f
fix tests
schiller-manuel Jul 7, 2024
1df34b5
rename file to match tests
schiller-manuel Jul 7, 2024
d00653d
move redirect tests into redirect.test.tsx
schiller-manuel Jul 7, 2024
a7eb599
fix: automatically reset route-controlled error boundaries with `rout…
tannerlinsley Jul 8, 2024
752a136
fix(router): notfound() now works after longer delay, beforeLoad + no…
tannerlinsley Jul 8, 2024
a90c8b9
fix: more fixes for error boundary resetting
tannerlinsley Jul 8, 2024
040f826
fix: remove unnecessary destructure
tannerlinsley Jul 8, 2024
e303f1c
style(router-devtools): remove unused import and fix the eslint impor…
SeanCassiere Jul 8, 2024
ab2caa1
fix: move getMatch inside of getLoaderContext
tannerlinsley Jul 8, 2024
37c28ca
fix: oops, typescript prematurely stripped code after an early return…
tannerlinsley Jul 9, 2024
556a0a5
Merge branch 'main' into fix-context-issues
SeanCassiere Jul 9, 2024
1e328f8
fix(react-router): issues from the merge conflict resolution
SeanCassiere Jul 9, 2024
3f5db3a
fix: allow history subscribers to be observed
tannerlinsley Jul 12, 2024
582a0e0
fix: granular promise sharing for beforeLoad/loader/lazy
tannerlinsley Jul 12, 2024
8abb1e8
fix: test lint
tannerlinsley Jul 12, 2024
d690b0a
fix: Do not update active matches during preloading, but use latest m…
tannerlinsley Jul 12, 2024
66576f5
test(react-router): check for `parentMatchPromise` being defined in t…
SeanCassiere Jul 13, 2024
7d8b5a0
fix: supply parentMatchPromise again
tannerlinsley Jul 13, 2024
64a8bdd
Merge branch 'main' into fix-context-issues
schiller-manuel Jul 13, 2024
0f34fbe
fix: better MatchInner reactivity
tannerlinsley Jul 14, 2024
bdb00d5
style(react-router): prettier on `Match.tsx`
SeanCassiere Jul 15, 2024
94856f6
Merge branch 'main' into fix-context-issues
SeanCassiere Jul 15, 2024
cdc141f
style(react-router): prettier on `router.ts`
SeanCassiere Jul 15, 2024
2f9eeaa
Merge branch 'main' into fix-context-issues
SeanCassiere Jul 15, 2024
b6f2c68
fix: more concise minPendingMatch promise
tannerlinsley Jul 15, 2024
91ad39e
fix: only disallow preload match updates to active matches **at the t…
tannerlinsley Jul 16, 2024
2c92c18
comment out log
tannerlinsley Jul 16, 2024
8a2979d
Merge branch 'main' into fix-context-issues
tannerlinsley Jul 16, 2024
649ead6
Merge branch 'main' into fix-context-issues
SeanCassiere Jul 16, 2024
4427e2e
style(react-router): resolve prettier on `Match.tsx`
SeanCassiere Jul 16, 2024
2a85a58
fix types
schiller-manuel Jul 16, 2024
50f0b4a
fix tests
schiller-manuel Jul 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions docs/framework/react/guide/data-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ export const Route = createFileRoute('/posts')({
})
```

The `reset` function can be used to show a `retry` button. If you want to retry the route loading, you need to additionally call `router.invalidate()`:
The `reset` function can be used to allow the user to retry rendering the error boundaries normal children:

```tsx
// routes/posts.tsx
Expand All @@ -468,7 +468,31 @@ export const Route = createFileRoute('/posts')({
onClick={() => {
// Reset the router error boundary
reset()
// Invalidate the route to reload the loader
}}
>
retry
</button>
</div>
)
},
})
```

If the error was the result of a route load, you should instead call `router.invalidate()`, which will coordinate both a router reload and an error boundary reset:

```tsx
// routes/posts.tsx
export const Route = createFileRoute('/posts')({
loader: () => fetchPosts(),
errorComponent: ({ error, reset }) => {
const router = useRouter()

return (
<div>
{error.message}
<button
onClick={() => {
// Invalidate the route to reload the loader, which will also reset the error boundary
router.invalidate()
}}
>
Expand Down
4 changes: 1 addition & 3 deletions docs/framework/react/guide/external-data-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,7 @@ export const Route = createFileRoute('/posts')({
{error.message}
<button
onClick={() => {
// Reset the router error boundary
reset()
// Invalidate the route to reload the loader
// Invalidate the route to reload the loader, and reset any router error boundaries
router.invalidate()
}}
>
Expand Down
6 changes: 3 additions & 3 deletions docs/framework/react/guide/server-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function Component() {
if (!el) return
el.addEventListener('click', async () => {
const result = await yourFn()
console.log(result)
console.info(result)
})
}

Expand All @@ -56,7 +56,7 @@ Or from another server function:
```typescript
const yourFn2 = createServerFn('POST', async () => {
const result = await yourFn()
console.log(result)
console.info(result)
})
```

Expand Down Expand Up @@ -140,7 +140,7 @@ const yourFn = createServerFn('POST', async () => {
// Server-side code lives here
})

console.log(yourFn.url)
console.info(yourFn.url)
```

And pass this to the `action` attribute of the form:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export function PostErrorComponent({ error, reset }: ErrorComponentProps) {
<div>
<button
onClick={() => {
reset()
router.invalidate()
}}
>
Expand Down
1 change: 0 additions & 1 deletion examples/react/basic-react-query/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,6 @@ function PostErrorComponent({ error, reset }: ErrorComponentProps) {
<div>
<button
onClick={() => {
reset()
router.invalidate()
}}
>
Expand Down
4 changes: 1 addition & 3 deletions examples/react/start-trellaux/app/components/Column.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import invariant from 'tiny-invariant'
import { twMerge } from 'tailwind-merge'

import { flushSync } from 'react-dom'
import { CONTENT_TYPES, INTENTS, type RenderedItem } from '../types'
import { CONTENT_TYPES, type RenderedItem } from '../types'
import { Icon } from '../icons/icons'
import {
useDeleteColumnMutation,
Expand Down Expand Up @@ -119,8 +119,6 @@ export const Column = forwardRef<HTMLDivElement, ColumnProps>(
acceptColumnDrop === 'left' ? previousOrder : nextOrder
const moveOrder = (droppedOrder + order) / 2

console.log('moveOrder', moveOrder)

updateColumnMutation.mutate({
boardId,
id: transfer.id,
Expand Down
6 changes: 6 additions & 0 deletions packages/history/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface NavigateOptions {
}
export interface RouterHistory {
location: HistoryLocation
subscribers: Set<() => void>
Comment thread
tannerlinsley marked this conversation as resolved.
subscribe: (cb: () => void) => () => void
push: (path: string, state?: any, navigateOpts?: NavigateOptions) => void
replace: (path: string, state?: any, navigateOpts?: NavigateOptions) => void
Expand Down Expand Up @@ -100,6 +101,7 @@ export function createHistory(opts: {
get location() {
return location
},
subscribers,
subscribe: (cb: () => void) => {
subscribers.add(cb)

Expand Down Expand Up @@ -269,6 +271,10 @@ export function createBrowserHistory(opts?: {
}

if (!scheduled) {
if (process.env.NODE_ENV === 'test') {
flush()
return
}
// Schedule an update to the browser history
scheduled = Promise.resolve().then(() => flush())
}
Expand Down
2 changes: 1 addition & 1 deletion packages/react-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"test:types:ts54": "node ../../node_modules/typescript54/lib/tsc.js -p tsconfig.legacy.json",
"test:types:ts55": "tsc",
"test:unit": "vitest",
"test:unit:dev": "pnpm run test:unit --watch",
"test:unit:dev": "pnpm run test:unit --watch --hideSkippedTests",
"test:build": "publint --strict",
"build": "vite build"
},
Expand Down
10 changes: 7 additions & 3 deletions packages/react-router/src/CatchBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ErrorRouteComponent } from './route'
import type { ErrorInfo } from 'react'

export function CatchBoundary(props: {
getResetKey: () => string
getResetKey: () => number | string
children: React.ReactNode
errorComponent?: ErrorRouteComponent
onCatch?: (error: Error, errorInfo: ErrorInfo) => void
Expand All @@ -29,7 +29,7 @@ export function CatchBoundary(props: {
}

class CatchBoundaryImpl extends React.Component<{
getResetKey: () => string
getResetKey: () => number | string
children: (props: {
error: Error | null
reset: () => void
Expand Down Expand Up @@ -64,8 +64,12 @@ class CatchBoundaryImpl extends React.Component<{
}
}
render() {
// If the resetKey has changed, don't render the error
return this.props.children({
error: this.state.error,
error:
this.state.resetKey !== this.props.getResetKey()
? null
: this.state.error,
reset: () => {
this.reset()
},
Expand Down
81 changes: 45 additions & 36 deletions packages/react-router/src/Match.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function Match({ matchId }: { matchId: string }) {
: SafeFragment

const resetKey = useRouterState({
select: (s) => s.resolvedLocation.state.key!,
select: (s) => s.loadedAt,
})

return (
Expand Down Expand Up @@ -108,23 +108,44 @@ function MatchInner({ matchId }: { matchId: string }): any {

const route = router.routesById[routeId]!

const [match, matchIndex] = useRouterState({
const matchIndex = useRouterState({
select: (s) => {
return s.matches.findIndex((d) => d.id === matchId)
},
})

const match = useRouterState({
select: (s) => {
const matchIndex = s.matches.findIndex((d) => d.id === matchId)
const match = s.matches[matchIndex]!
return [
pick(match, [
'id',
'status',
'error',
'loadPromise',
'minPendingPromise',
]),
matchIndex,
] as const
return pick(match, [
'id',
'status',
'error',
'loadPromise',
'minPendingPromise',
])
},
})

// function useChangedDiff(value: any) {
// const ref = React.useRef(value)
// const changed = ref.current !== value
// if (changed) {
// console.log(
// 'Changed:',
// value,
// Object.fromEntries(
// Object.entries(value).filter(
// ([key, val]) => val !== ref.current[key],
// ),
// ),
// )
// }
// ref.current = value
// }

// useChangedDiff(match)

const RouteErrorComponent =
(route.options.errorComponent ?? router.options.defaultErrorComponent) ||
ErrorComponent
Expand Down Expand Up @@ -190,36 +211,24 @@ function MatchInner({ matchId }: { matchId: string }): any {

if (pendingMinMs && !match.minPendingPromise) {
// Create a promise that will resolve after the minPendingMs
match.minPendingPromise = createControlledPromise()

if (!router.isServer) {
const minPendingPromise = createControlledPromise<void>()

Promise.resolve().then(() => {
router.__store.setState((s) => ({
...s,
matches: s.matches.map((d) =>
d.id === match.id
? { ...d, minPendingPromise: createControlledPromise() }
: d,
),
router.updateMatch(match.id, (prev) => ({
...prev,
minPendingPromise,
}))
})

setTimeout(() => {
minPendingPromise.resolve()

// We've handled the minPendingPromise, so we can delete it
router.__store.setState((s) => {
return {
...s,
matches: s.matches.map((d) =>
d.id === match.id
? {
...d,
minPendingPromise:
(d.minPendingPromise?.resolve(), undefined),
}
: d,
),
}
})
router.updateMatch(match.id, (prev) => ({
...prev,
minPendingPromise: undefined,
}))
}, pendingMinMs)
}
}
Expand Down
8 changes: 5 additions & 3 deletions packages/react-router/src/Matches.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ export interface RouteMatch<
paramsError: unknown
searchError: unknown
updatedAt: number
loadPromise: ControlledPromise<void>
loaderPromise: Promise<TLoaderData>
componentsPromise?: Promise<Array<void>>
loadPromise?: ControlledPromise<void>
beforeLoadPromise?: ControlledPromise<void>
loaderPromise?: ControlledPromise<void>
loaderData?: TLoaderData
routeContext: TRouteContext
context: TAllContext
Expand Down Expand Up @@ -132,7 +134,7 @@ function MatchesInner() {
})

const resetKey = useRouterState({
select: (s) => s.resolvedLocation.state.key!,
select: (s) => s.loadedAt,
})

return (
Expand Down
11 changes: 0 additions & 11 deletions packages/react-router/src/RouterProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,6 @@ export function RouterProvider<
)
}

export function getRouteMatch<TRouteTree extends AnyRoute>(
state: RouterState<TRouteTree>,
id: string,
): undefined | MakeRouteMatch<TRouteTree> {
return [
...state.cachedMatches,
...(state.pendingMatches ?? []),
...state.matches,
].find((d) => d.id === id)
}

export type RouterProps<
TRouter extends AnyRouter = RegisteredRouter,
TDehydrated extends Record<string, any> = Record<string, any>,
Expand Down
6 changes: 5 additions & 1 deletion packages/react-router/src/Transitioner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react'
import { pick, useLayoutEffect, usePrevious } from './utils'
import { useRouter } from './useRouter'
import { useRouterState } from './useRouterState'
import { trimPathRight } from '.'

export function Transitioner() {
const router = useRouter()
Expand Down Expand Up @@ -40,7 +41,10 @@ export function Transitioner() {
state: true,
})

if (router.state.location.href !== nextLocation.href) {
if (
trimPathRight(router.latestLocation.href) !==
trimPathRight(nextLocation.href)
) {
router.commitLocation({ ...nextLocation, replace: true })
}

Expand Down
1 change: 0 additions & 1 deletion packages/react-router/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ export {
export {
RouterProvider,
RouterContextProvider,
getRouteMatch,
type RouterProps,
type CommitLocationOptions,
type MatchLocation,
Expand Down
1 change: 1 addition & 0 deletions packages/react-router/src/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,7 @@ export class Route<
router?: AnyRouter
rank!: number
lazyFn?: () => Promise<LazyRoute<any>>
_lazyPromise?: Promise<void>

/**
* @deprecated Use the `createRoute` function instead.
Expand Down
Loading