Skip to content

Commit

Permalink
fix: loader state isLoading and isFetching
Browse files Browse the repository at this point in the history
  • Loading branch information
tannerlinsley committed Jul 28, 2023
1 parent d73c871 commit 3ab3506
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 61 deletions.
70 changes: 48 additions & 22 deletions docs/guide/data-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,26 +64,31 @@ import { Route } from '@tanstack/router'
import { Loader, useLoader } from '@tanstack/react-loaders'

const postsLoader = new Loader({
key: 'posts',
fn: async (params) => {
const res = await fetch(`/api/posts`)
if (!res.ok) throw new Error('Failed to fetch posts')
return res.json()
},
})

const loaderClient = new LoaderClient({
loaders: [postsLoader],
})

const postsRoute = new Route({
getParentPath: () => rootRoute,
path: 'posts',
fn: async () => {
// Ensure our loader is loaded
await postsLoader.load()
await loaderClient.load({ key: 'posts' })

// Return a hook fn we can use in our component
return () => useLoader({ loader: postsLoader })
return () => useLoaderInstance({ key: 'posts' })
},
component: ({ useLoader }) => {
// Access the hook we made in the loader function (and call it)
const posts = useLoader()()
const { data: posts } = useLoader()()

return <div>...</div>
},
Expand All @@ -100,7 +105,7 @@ The `loader` function receives a single parameter, which is an object with the f
- `hash` - The route's hash
- `context` - The route's context object **including** inherited context from parent routes
- `routeContext` - The route's context object, **excluding** inherited context from parent routes
- `signal` - The route's abort signal which is cancelled when the route is unloaded or when the `loader` call becomes outdated.
- `abortController` - The route's abortController. It's signal is cancelled when the route is unloaded or when the `loader` call becomes outdated.

Using these parameters, we can do a lot of cool things. Let's take a look at a few examples

Expand All @@ -113,6 +118,7 @@ import { Route } from '@tanstack/router'
import { Loader, useLoader } from '@tanstack/react-loaders'

const postLoader = new Loader({
key: 'post',
// Accept a postId string variable
fn: async (postId: string) => {
const res = await fetch(`/api/posts/${postId}`)
Expand All @@ -121,17 +127,21 @@ const postLoader = new Loader({
},
})

const loaderClient = new LoaderClient({
loaders: [postLoader],
})

const postRoute = new Route({
getParentPath: () => postsRoute,
path: '$postId',
async loader({ params }) {
// Load our loader
await postLoader.load({ variables: params.postId })
await loaderClient.loade({ key: 'post', variables: params.postId })
// Return a hook fn we can use in our component
return () => useLoader({ loader: postLoader, variables: params.postId })
return () => useLoaderInstance({ key: 'post', variables: params.postId })
},
component: ({ useLoader }) => {
const posts = useLoader()()
const { data: posts } = useLoader()()

return <div>...</div>
},
Expand All @@ -147,6 +157,7 @@ import { Route } from '@tanstack/router'
import { Loader, useLoader } from '@tanstack/react-loaders'

const postsLoader = new Loader({
key: 'posts',
// Accept a page number variable
fn: async (pageIndex: number) => {
const res = await fetch(`/api/posts?page=${pageIndex}`)
Expand All @@ -155,6 +166,10 @@ const postsLoader = new Loader({
},
})

const loaderClient = new LoaderClient({
loaders: [postsLoader],
})

const postsRoute = new Route({
getParentPath: () => rootRoute,
path: 'posts',
Expand All @@ -163,12 +178,13 @@ const postsRoute = new Route({
}),
async loader({ search }) {
// Load our loader
await postsLoader.load({ variables: search.pageIndex })
await loaderClient.load({ key: 'posts', variables: search.pageIndex })
// Return a hook fn we can use in our component
return () => useLoader({ loader: postsLoader, variables: search.pageIndex })
return () =>
useLoaderInstance({ key: 'posts', variables: search.pageIndex })
},
component: ({ useLoader }) => {
const posts = useLoader()()
const { data: posts } = useLoader()()

return <div>...</div>
},
Expand All @@ -186,6 +202,7 @@ import { Route } from '@tanstack/router'
import { Loader, useLoader } from '@tanstack/react-loaders'

const postsLoader = new Loader({
key: 'posts',
fn: async () => {
const res = await fetch(`/api/posts`)
if (!res.ok) throw new Error('Failed to fetch posts')
Expand Down Expand Up @@ -213,12 +230,11 @@ const postsRoute = new Route({
getParentPath: () => rootRoute,
path: 'posts',
async loader({ context: { loaderClient } }) {
const { postsLoader } = loaderClient
await postsLoader.load()
return () => useLoader({ loader: postsLoader })
await loaderClient.load({ key: 'posts' })
return () => useLoaderInstance({ key: 'posts' })
},
component: ({ useLoader }) => {
const posts = useLoader()()
const { data: posts } = useLoader()()

return <div>...</div>
},
Expand All @@ -239,13 +255,14 @@ const router = new Router({

## Using the Abort Signal

The `signal` property of the `loader` function is an [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) which is cancelled when the route is unloaded or when the `loader` call becomes outdated. This is useful for cancelling network requests when the route is unloaded or when the route's params change. Here is an example using TanStack Loader's signal passthrough:
The `abortControler` property of the `loader` function is an [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController). Its signal is cancelled when the route is unloaded or when the `loader` call becomes outdated. This is useful for cancelling network requests when the route is unloaded or when the route's params change. Here is an example using TanStack Loader's signal passthrough:

```tsx
import { Route } from '@tanstack/router'
import { Loader, useLoader } from '@tanstack/react-loaders'

const postsLoader = new Loader({
key: 'posts',
// Accept a page number variable
fn: async (pageIndex: number, { signal }) => {
const res = await fetch(`/api/posts?page=${pageIndex}`, { signal })
Expand All @@ -254,16 +271,20 @@ const postsLoader = new Loader({
},
})

const loaderClient = new LoaderClient({
loaders: [postsLoader],
})

const postsRoute = new Route({
getParentPath: () => rootRoute,
path: 'posts',
async loader({ signal }) {
async loader({ abortController }) {
// Pass the route's signal to the loader
await postsLoader.load({ signal })
return () => useLoader({ loader: postsLoader })
await loaderClient.load({ key: 'posts', signal: abortController.signal })
return () => useLoaderInstance({ key: 'posts' })
},
component: ({ useLoader }) => {
const posts = useLoader()()
const { data: posts } = useLoader()()

return <div>...</div>
},
Expand All @@ -279,23 +300,28 @@ import { Route } from '@tanstack/router'
import { Loader, useLoader } from '@tanstack/react-loaders'

const postsLoader = new Loader({
key: 'posts',
fn: async () => {
const res = await fetch(`/api/posts?page=${pageIndex}`)
if (!res.ok) throw new Error('Failed to fetch posts')
return res.json()
},
})

const loaderClient = new LoaderClient({
loaders: [postsLoader],
})

const postsRoute = new Route({
getParentPath: () => rootRoute,
path: 'posts',
async loader({ preload }) {
// Pass the route's preload to the loader
await postsLoader.load({ preload })
return () => useLoader({ loader: postsLoader })
await loaderClient.load({ key: 'posts', preload })
return () => useLoaderInstance({ loader: postsLoader })
},
component: ({ useLoader }) => {
const posts = useLoader()()
const { data: posts } = useLoader()()

return <div>...</div>
},
Expand Down
13 changes: 9 additions & 4 deletions docs/guide/data-mutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Let's write a data mutation that will update a post on a server. We'll use TanSt
import { Action } from '@tanstack/actions'

const updatePostAction = new Action({
key: 'updatePost',
fn: async (post: Post) => {
const response = await fetch(`/api/posts/${post.id}`, {
method: 'PATCH',
Expand Down Expand Up @@ -90,14 +91,15 @@ So how does my data loader get the updated data? **Invalidation**. When you muta
import { Action } from '@tanstack/actions'

const updatePostAction = new Action({
key: 'updatePost',
fn: async (post: Post) => {
//...
},
onEachSuccess: () => {
// Invalidate the posts loader. Depending on your data loading library,
// this may result in an immediate refetch or it could simply mark
// the data as stale and refetch it the next time it's used.
postsLoader.invalidate()
loaderClient.invalidateLoader({ key: 'posts' })
},
})
```
Expand All @@ -110,13 +112,14 @@ Again, we'll assume we're using TanStack Actions here, but it's also possible to
import { Action } from '@tanstack/actions'

const updatePostAction = new Action({
key: 'updatePost',
fn: async (post: Post) => {
//...
},
onEachSuccess: (submission) => {
// Use the submission payload to invalidate the specific post
const post = submission.payload
postsLoader.invalidateInstance({ variables: post.id })
loaderClient.invalidateInstance({ key: 'post', variables: post.id })
},
})
```
Expand All @@ -129,11 +132,12 @@ It's very common to invalidate an entire subset of data based on hierarchy when
import { Action } from '@tanstack/actions'

const updatePostAction = new Action({
key: 'updatePost',
fn: async (post: Post) => {
//...
},
onEachSuccess: (submission) => {
postsLoader.invalidate()
loaderClient.invalidateLoader({ key: 'post' })
},
})
```
Expand Down Expand Up @@ -201,11 +205,12 @@ This is a great place to reset your old mutation/actions states. We'll use TanSt

```tsx
const updatePostAction = new Action({
key: 'updatePost',
fn: async (post: Post) => {
//...
},
onEachSuccess: (submission) => {
postsLoader.invalidate()
loaderClient.invalidateLoader({ key: 'posts' })
},
})

Expand Down
2 changes: 1 addition & 1 deletion docs/guide/preloading.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const postsRoute = new Route({
path: 'posts',
component: PostsComponent,
loader: async ({ preload }) => {
postsLoader.load({ preload })
await loaderClient.load({ key: 'posts', preload })
},
})
```
Expand Down
5 changes: 4 additions & 1 deletion docs/guide/router-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ const userRoute = new Route({
path: 'todos',
component: Todos,
loader: ({ context }) => {
await todosLoader.load({ variables: { user: context.user.id } })
await loaderClient.load({
key: 'todos',
variables: { user: context.user.id },
})
},
})
```
Expand Down
9 changes: 5 additions & 4 deletions docs/guide/ssr-and-streaming.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,9 +384,9 @@ export function createRouter() {
loaderClient.options = {
...loaderClient.options,
hydrateLoaderInstanceFn: (instance) =>
router.hydrateData(instance.hashedKey) as any,
router.hydrateData(instance.hashedKey),
dehydrateLoaderInstanceFn: (instance) =>
router.dehydrateData(instance.hashedKey, () => instance.state),
router.dehydrateData(instance.hashedKey, () => instance),
}

return router
Expand All @@ -402,6 +402,7 @@ import * as React from 'react'
import { Loader } from '@tanstack/react-loaders'

const testLoader = new Loader({
key: 'test',
fn: async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return 'Hello World!'
Expand All @@ -417,8 +418,8 @@ export function Test() {
}

export function Inner() {
const instance = testLoader.useLoader()
const instance = useLoaderInstance({ key: 'test' })

return instance.state.data
return instance.data
}
```
4 changes: 2 additions & 2 deletions examples/react/basic/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,13 @@ const postsRoute = new Route({
return () => useLoaderInstance({ key: 'posts' })
},
component: ({ useLoader }) => {
const postsLoader = useLoader()()
const { data: posts } = useLoader()()

return (
<div className="p-2 flex gap-2">
<ul className="list-disc pl-4">
{[
...postsLoader.data,
...posts,
{ id: 'i-do-not-exist', title: 'Non-existent Post' },
]?.map((post) => {
return (
Expand Down
1 change: 1 addition & 0 deletions examples/react/wip-start-basic/src/app/routes/posts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type PostType = {
}

export const postsLoader = new Loader({
key: 'posts',
fn: fetch$(async () => {
console.log('Fetching posts...')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Route } from '@tanstack/router'
import { postsLoader, postsRoute, PostType } from '../posts'

export const postLoader = new Loader({
key: 'post',
fn: fetch$(
async (postId: string) => {
console.log(`Fetching post with id ${postId}...`)
Expand Down
Loading

0 comments on commit 3ab3506

Please sign in to comment.