Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 40 additions & 0 deletions docs/rtk-query/api/createApi.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,46 @@ _(optional, not applicable with `queryFn`)_

[summary](docblock://query/endpointDefinitions.ts?token=EndpointDefinitionWithQuery.rawErrorResponseSchema)

A [Standard Schema](https://standardschema.dev/) compliant schema that validates the error response returned by your `baseQuery` **before** it is passed to [`transformErrorResponse`](#transformerrorresponse). This lets you catch unexpected error shapes from the server early, before any transformation logic runs.

This is the error-side counterpart to [`rawResponseSchema`](#rawresponseschema), which does the same for successful responses.

:::info How `rawErrorResponseSchema` differs from `errorResponseSchema`

- **`rawErrorResponseSchema`** — validates the _raw_ error value straight from `baseQuery`, _before_ `transformErrorResponse` runs. Use this when you want to ensure the server's error response matches the shape your `transformErrorResponse` function expects as input.
- **`errorResponseSchema`** — validates the error value _after_ `transformErrorResponse` has run (or the raw value if no transform is provided). Use this when you want to guarantee the final error shape that your UI components will consume.

You can use both on the same endpoint to validate at each stage of the pipeline.

:::

```ts title="rawErrorResponseSchema usage" no-transpile
import { createApi } from '@reduxjs/toolkit/query/react'
import * as v from 'valibot'

// Schema matching the raw error shape returned by your baseQuery
const rawApiErrorSchema = v.object({
status: v.number(),
data: v.object({
code: v.string(),
message: v.string(),
}),
})

const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (build) => ({
getPost: build.query<Post, number>({
query: (id) => `posts/${id}`,
// Validates the raw error from baseQuery BEFORE transformErrorResponse
rawErrorResponseSchema: rawApiErrorSchema,
// Extracts just the nested error data
transformErrorResponse: (raw) => raw.data,
}),
}),
})
```

[examples](docblock://query/endpointDefinitions.ts?token=EndpointDefinitionWithQuery.rawErrorResponseSchema)

#### `metaSchema`
Expand Down
62 changes: 38 additions & 24 deletions docs/usage/nextjs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export type AppDispatch = AppStore['dispatch']

// file: app/StoreProvider.tsx
'use client'
import { useRef } from 'react'
import { useState } from 'react'
import { Provider } from 'react-redux'
// highlight-start
import { makeStore, AppStore } from '../lib/store'
Expand All @@ -181,18 +181,33 @@ export default function StoreProvider({
children: React.ReactNode
}) {
// highlight-start
const storeRef = useRef<AppStore>(undefined)
if (!storeRef.current) {
// Create the store instance the first time this renders
storeRef.current = makeStore()
}
// Create the store instance once using a lazy initializer
const [store] = useState(() => makeStore())
// highlight-end

return <Provider store={storeRef.current}>{children}</Provider>
return <Provider store={store}>{children}</Provider>
}
```

In this example code we are ensuring that this client component is re-render safe by checking the value of the reference to ensure that the store is only created once. This component will only be rendered once per request on the server, but might be re-rendered multiple times on the client if there are stateful client components located above this component in the tree, or if this component also contains other mutable state that causes a re-render.
In this example code we are ensuring that this client component is re-render safe by using `useState` with a lazy initializer function. React guarantees the initializer only runs once, so the store is created exactly one time. This component will only be rendered once per request on the server, but might be re-rendered multiple times on the client if there are stateful client components located above this component in the tree, or if this component also contains other mutable state that causes a re-render.

:::warning React 19 Breaking Change

Previous versions of this guide used a `useRef`-based pattern to create the store during render:

```ts no-transpile
// ❌ This pattern breaks in React 19
const storeRef = useRef<AppStore>(undefined)
if (!storeRef.current) {
storeRef.current = makeStore()
}
```

**React 19 no longer allows accessing `ref.current` during render.** Doing so will throw: `"Cannot access refs during render"`. This applies to both React 19 Strict Mode and concurrent features.

The `useState` lazy initializer pattern shown above is the recommended replacement. It is safe for concurrent rendering and works with all versions of React.

:::

:::tip Why Client Components?

Expand Down Expand Up @@ -248,7 +263,7 @@ export type AppDispatch = AppStore['dispatch']

// file: app/StoreProvider.tsx
'use client'
import { useRef } from 'react'
import { useState } from 'react'
import { Provider } from 'react-redux'
import { makeStore, AppStore } from '../lib/store'
// highlight-start
Expand All @@ -262,15 +277,15 @@ export default function StoreProvider({
count: number
children: React.ReactNode
}) {
const storeRef = useRef<AppStore | null>(null)
if (!storeRef.current) {
storeRef.current = makeStore()
// highlight-start
storeRef.current.dispatch(initializeCount(count))
// highlight-end
}

return <Provider store={storeRef.current}>{children}</Provider>
// highlight-start
const [store] = useState(() => {
const store = makeStore()
store.dispatch(initializeCount(count))
return store
})
// highlight-end

return <Provider store={store}>{children}</Provider>
}
```

Expand Down Expand Up @@ -342,7 +357,7 @@ export const useAppStore = useStore.withTypes<AppStore>()

// file: app/ProductName.tsx
'use client'
import { useRef } from 'react'
import { useState } from 'react'
import { useAppSelector, useAppDispatch, useAppStore } from '../lib/hooks'
import {
initializeProduct,
Expand All @@ -354,11 +369,10 @@ export default function ProductName({ product }: { product: Product }) {
// highlight-start
// Initialize the store with the product information
const store = useAppStore()
const initialized = useRef(false)
if (!initialized.current) {
const [initialized] = useState(() => {
store.dispatch(initializeProduct(product))
initialized.current = true
}
return true
})
const name = useAppSelector((state) => state.product.name)
const dispatch = useAppDispatch()
// highlight-end
Expand All @@ -372,7 +386,7 @@ export default function ProductName({ product }: { product: Product }) {
}
```

Here we are using the same initialization pattern as before, of dispatching actions to the store, to set the route-specific data. The `initialized` ref is used to ensure that the store is only initialized once per route change.
Here we are using the same initialization pattern as before, of dispatching actions to the store, to set the route-specific data. The `useState` lazy initializer is used to ensure that the store is only initialized once per route change.

It is worth noting that initializing the store with a `useEffect` would not work because `useEffect` only runs on the client. This would result in hydration errors or flicker because the result from a server side render would not match the result from the client side render.

Expand Down
Loading