Skip to content

Commit

Permalink
Merge branch 'master' into v2.0-integration
Browse files Browse the repository at this point in the history
# Conflicts:
#	packages/toolkit/package.json
#	packages/toolkit/src/configureStore.ts
#	packages/toolkit/src/createAction.ts
#	packages/toolkit/src/listenerMiddleware/index.ts
#	packages/toolkit/src/query/tests/createApi.test.ts
#	packages/toolkit/src/tsHelpers.ts
#	packages/toolkit/src/utils.ts
#	website/package.json
#	yarn.lock
  • Loading branch information
markerikson committed Apr 18, 2023
2 parents 34db089 + d3bb412 commit 417d3de
Show file tree
Hide file tree
Showing 36 changed files with 557 additions and 126 deletions.
9 changes: 5 additions & 4 deletions docs/api/configureStore.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ to the store setup for a better development experience.

```ts no-transpile
type ConfigureEnhancersCallback = (
defaultEnhancers: StoreEnhancer[]
defaultEnhancers: EnhancerArray<[StoreEnhancer]>
) => StoreEnhancer[]

interface ConfigureStoreOptions<
Expand Down Expand Up @@ -107,7 +107,8 @@ a list of the specific options that are available.
Defaults to `true`.

#### `trace`
The Redux DevTools Extension recently added [support for showing action stack traces](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/Features/Trace.md) that show exactly where each action was dispatched.

The Redux DevTools Extension recently added [support for showing action stack traces](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/Features/Trace.md) that show exactly where each action was dispatched.
Capturing the traces can add a bit of overhead, so the DevTools Extension allows users to configure whether action stack traces are captured by [setting the 'trace' argument](https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Arguments.md#trace).
If the DevTools are enabled by passing `true` or an object, then `configureStore` will default to enabling capturing action stack traces in development mode only.

Expand All @@ -129,7 +130,7 @@ If defined as a callback function, it will be called with the existing array of
and should return a new array of enhancers. This is primarily useful for cases where a store enhancer needs to be added
in front of `applyMiddleware`, such as `redux-first-router` or `redux-offline`.

Example: `enhancers: (defaultEnhancers) => [offline, ...defaultEnhancers]` will result in a final setup
Example: `enhancers: (defaultEnhancers) => defaultEnhancers.prepend(offline)` will result in a final setup
of `[offline, applyMiddleware, devToolsExtension]`.

## Usage
Expand Down Expand Up @@ -195,7 +196,7 @@ const preloadedState = {
visibilityFilter: 'SHOW_COMPLETED',
}
const debounceNotify = _.debounce(notify => notify());
const debounceNotify = _.debounce((notify) => notify())
const store = configureStore({
reducer,
Expand Down
4 changes: 3 additions & 1 deletion docs/api/createListenerMiddleware.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,8 @@ These methods provide the ability to write conditional logic based on future dis

Both these methods are cancellation-aware, and will throw a `TaskAbortError` if the listener instance is cancelled while paused.

Note that both `take` and `condition` will only resolve **after the next action** has been dispatched. They do not resolve immediately even if their predicate would return true for the current state.

### Child Tasks

- `fork: (executor: (forkApi: ForkApi) => T | Promise<T>) => ForkedTask<T>`: Launches a "child task" that may be used to accomplish additional work. Accepts any sync or async function as its argument, and returns a `{result, cancel}` object that can be used to check the final status and return value of the child task, or cancel it while in-progress.
Expand Down Expand Up @@ -831,7 +833,7 @@ First, you can import effect callbacks from slice files into the middleware file
```ts no-transpile title="app/listenerMiddleware.ts"
import { action1, listener1 } from '../features/feature1/feature1Slice'
import { action2, listener2 } from '../features/feature2/feature1Slice'
import { action2, listener2 } from '../features/feature2/feature2Slice'

listenerMiddleware.startListening({ actionCreator: action1, effect: listener1 })
listenerMiddleware.startListening({ actionCreator: action2, effect: listener2 })
Expand Down
15 changes: 8 additions & 7 deletions docs/rtk-query/api/created-api/endpoints.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ When dispatching an action creator, you're responsible for storing a reference t

#### Example

```tsx title="initiate query example"
```tsx no-transpile title="initiate query example"
import { useState } from 'react'
import { useAppDispatch } from './store/hooks'
import { api } from './services/api'
Expand All @@ -119,7 +119,7 @@ function App() {
}
```

```tsx title="initiate mutation example"
```tsx no-transpile title="initiate mutation example"
import { useState } from 'react'
import { useAppDispatch } from './store/hooks'
import { api, Post } from './services/api'
Expand Down Expand Up @@ -187,7 +187,7 @@ Each call to `.select(someCacheKey)` returns a _new_ selector function instance.
#### Example
```tsx title="select query example"
```tsx no-transpile title="select query example"
import { useState, useMemo } from 'react'
import { useAppDispatch, useAppSelector } from './store/hooks'
import { api } from './services/api'
Expand All @@ -198,9 +198,10 @@ function App() {
// highlight-start
// useMemo is used to only call `.select()` when required.
// Each call will create a new selector function instance
const selectPost = useMemo(() => api.endpoints.getPost.select(postId), [
postId,
])
const selectPost = useMemo(
() => api.endpoints.getPost.select(postId),
[postId]
)
const { data, isLoading } = useAppSelector(selectPost)
// highlight-end

Expand All @@ -223,7 +224,7 @@ function App() {
}
```

```tsx title="select mutation example"
```tsx no-transpile title="select mutation example"
import { useState, useMemo } from 'react'
import { skipToken } from '@reduxjs/toolkit/query'
import { useAppDispatch, useAppSelector } from './store/hooks'
Expand Down
17 changes: 8 additions & 9 deletions docs/rtk-query/usage-with-typescript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ export const api = createApi({
export const { useGetPostQuery } = api
```
```tsx title="Using skip in a component"
```tsx no-transpile title="Using skip in a component"
import { useGetPostQuery } from './api'
function MaybePost({ id }: { id?: number }) {
Expand All @@ -486,7 +486,7 @@ While you might be able to convince yourself that the query won't be called unle

RTK Query provides a `skipToken` export which can be used as an alternative to the `skip` option in order to skip queries, while remaining type-safe. When `skipToken` is passed as the query argument to `useQuery`, `useQueryState` or `useQuerySubscription`, it provides the same effect as setting `skip: true` in the query options, while also being a valid argument in scenarios where the `arg` might be undefined otherwise.

```tsx title="Using skipToken in a component"
```tsx no-transpile title="Using skipToken in a component"
import { skipToken } from '@reduxjs/toolkit/query/react'
import { useGetPostQuery } from './api'

Expand Down Expand Up @@ -566,7 +566,7 @@ export interface SerializedError {
When using `fetchBaseQuery`, the `error` property returned from a hook will have the type `FetchBaseQueryError | SerializedError | undefined`.
If an error is present, you can access error properties after narrowing the type to either `FetchBaseQueryError` or `SerializedError`.

```tsx
```tsx no-transpile
import { api } from './services/api'

function PostDetail() {
Expand All @@ -587,10 +587,9 @@ function PostDetail() {
<div>{errMsg}</div>
</div>
)
}
else {
// you can access all properties of `SerializedError` here
return <div>{error.message}</div>
} else {
// you can access all properties of `SerializedError` here
return <div>{error.message}</div>
}
}

Expand All @@ -617,7 +616,7 @@ In order to safely access properties of the error, you must first narrow the typ
This can be done using a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates)
as shown below.

```tsx title="services/helpers.ts"
```tsx no-transpile title="services/helpers.ts"
import { FetchBaseQueryError } from '@reduxjs/toolkit/query'

/**
Expand All @@ -644,7 +643,7 @@ export function isErrorWithMessage(
}
```

```tsx title="addPost.tsx"
```tsx no-transpile title="addPost.tsx"
import { useState } from 'react'
import { useSnackbar } from 'notistack'
import { api } from './services/api'
Expand Down
8 changes: 4 additions & 4 deletions docs/rtk-query/usage/automated-refetching.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ const api = createApi({
Note that for the example above, the `id` is used where possible on a successful result. In the case of an error, no result is supplied, and we still consider that it has provided the general `'Post'` tag type rather than any specific instance of that tag.

:::tip Advanced List Invalidation
In order to provide stronger control over invalidating the appropriate data, you can use an arbitrary ID such a `'LIST'` for a given tag. See [Advanced Invalidation with abstract tag IDs](#advanced-invalidation-with-abstract-tag-ids) for additional details.
In order to provide stronger control over invalidating the appropriate data, you can use an arbitrary ID such as `'LIST'` for a given tag. See [Advanced Invalidation with abstract tag IDs](#advanced-invalidation-with-abstract-tag-ids) for additional details.
:::

### Invalidating cache data
Expand Down Expand Up @@ -381,7 +381,7 @@ const api = createApi({
For the example above, rather than invalidating any tag with the type `'Post'`, calling the `editPost` mutation function will now only invalidate a tag for the provided `id`. I.e. if cached data from an endpoint does not provide a `'Post'` for that same `id`, it will remain considered as 'valid', and will not be triggered to automatically re-fetch.
:::tip Using abstract tag IDs
In order to provide stronger control over invalidating the appropriate data, you can use an arbitrary ID such a `'LIST'` for a given tag. See [Advanced Invalidation with abstract tag IDs](#advanced-invalidation-with-abstract-tag-ids) for additional details.
In order to provide stronger control over invalidating the appropriate data, you can use an arbitrary ID such as `'LIST'` for a given tag. See [Advanced Invalidation with abstract tag IDs](#advanced-invalidation-with-abstract-tag-ids) for additional details.
:::
## Tag Invalidation Behavior
Expand Down Expand Up @@ -668,7 +668,7 @@ export const api = createApi({
export const { useGetPostsQuery, useGetPostQuery, useAddPostMutation } = api
```

```tsx title="App.tsx"
```tsx no-transpile title="App.tsx"
function App() {
const { data: posts } = useGetPostsQuery()
const [addPost] = useAddPostMutation()
Expand Down Expand Up @@ -742,7 +742,7 @@ export const api = createApi({
export const { useGetPostsQuery, useAddPostMutation, useGetPostQuery } = api
```

```tsx title="App.tsx"
```tsx no-transpile title="App.tsx"
function App() {
const { data: posts } = useGetPostsQuery()
const [addPost] = useAddPostMutation()
Expand Down
4 changes: 2 additions & 2 deletions docs/rtk-query/usage/cache-behavior.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ Calling the `refetch` function will force refetch the associated query.

Alternatively, you can dispatch the `initiate` thunk action for an endpoint, passing the option `forceRefetch: true` to the thunk action creator for the same effect.

```tsx title="Force refetch example"
```tsx no-transpile title="Force refetch example"
import { useDispatch } from 'react-redux'
import { useGetPostsQuery } from './api'

Expand Down Expand Up @@ -197,7 +197,7 @@ export const api = createApi({
})
```

```tsx title="Forcing refetch on component mount"
```tsx no-transpile title="Forcing refetch on component mount"
import { useGetPostsQuery } from './api'

const Component = () => {
Expand Down
25 changes: 12 additions & 13 deletions docs/rtk-query/usage/error-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ If your query or mutation happens to throw an error when using [fetchBaseQuery](

### Error Display Examples

```tsx title="Query Error"
```tsx no-transpile title="Query Error"
function PostsList() {
const { data, error } = useGetPostsQuery()

Expand All @@ -28,7 +28,7 @@ function PostsList() {
}
```

```tsx title="Mutation Error"
```tsx no-transpile title="Mutation Error"
function AddPost() {
const [addPost, { error }] = useAddPostMutation()

Expand All @@ -52,7 +52,7 @@ addPost({ id: 1, name: 'Example' })

:::

```tsx title="Manually selecting an error"
```tsx no-transpile title="Manually selecting an error"
function PostsList() {
const { error } = useSelector(api.endpoints.getPosts.select())

Expand Down Expand Up @@ -88,15 +88,14 @@ import { toast } from 'your-cool-library'
/**
* Log a warning and show a toast!
*/
export const rtkQueryErrorLogger: Middleware = (api: MiddlewareAPI) => (
next
) => (action) => {
// RTK Query uses `createAsyncThunk` from redux-toolkit under the hood, so we're able to utilize these matchers!
if (isRejectedWithValue(action)) {
console.warn('We got a rejected action!')
toast.warn({ title: 'Async error!', message: action.error.data.message })
export const rtkQueryErrorLogger: Middleware =
(api: MiddlewareAPI) => (next) => (action) => {
// RTK Query uses `createAsyncThunk` from redux-toolkit under the hood, so we're able to utilize these matchers!
if (isRejectedWithValue(action)) {
console.warn('We got a rejected action!')
toast.warn({ title: 'Async error!', message: action.error.data.message })
}

return next(action)
}

return next(action)
}
```
2 changes: 1 addition & 1 deletion docs/rtk-query/usage/manual-cache-updates.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ callback for a mutation without a good reason, as RTK Query is intended to be us
your cached data as a reflection of the server-side state.
:::

```tsx title="General manual cache update example"
```tsx no-transpile title="General manual cache update example"
import { api } from './api'
import { useAppDispatch } from './store/hooks'

Expand Down
8 changes: 4 additions & 4 deletions docs/rtk-query/usage/migrating-to-rtk-query.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export type RootState = ReturnType<typeof store.getState>
In order to have the store accessible within our app, we will wrap our `App` component with a [`Provider`](https://react-redux.js.org/api/provider) component from `react-redux`.
```tsx title="src/index.ts"
```tsx no-transpile title="src/index.ts"
import { render } from 'react-dom'
// highlight-start
import { Provider } from 'react-redux'
Expand Down Expand Up @@ -223,9 +223,9 @@ export type RootState = {
pokemon: typeof initialPokemonSlice
}

export declare const store: EnhancedStore<RootState>
export declare const store: EnhancedStore<RootState>
export type AppDispatch = typeof store.dispatch
export declare const useAppDispatch: () => (...args: any[])=> any;
export declare const useAppDispatch: () => (...args: any[]) => any

// file: src/hooks.ts
import { useEffect } from 'react'
Expand Down Expand Up @@ -276,7 +276,7 @@ Our implementation below provides the following behaviour in the component:
- When our component is mounted, if a request for the provided pokemon name has not already been sent for the session, send the request off
- The hook always provides the latest received `data` when available, as well as the request status booleans `isUninitialized`, `isPending`, `isFulfilled` & `isRejected` in order to determine the current UI at any given moment as a function of our state.

```tsx title="src/App.tsx"
```tsx no-transpile title="src/App.tsx"
import * as React from 'react'
// highlight-start
import { useGetPokemonByNameQuery } from './hooks'
Expand Down
8 changes: 6 additions & 2 deletions docs/rtk-query/usage/mutations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ const api = createApi({
// Pick out data and prevent nested properties in a hook or selector
transformResponse: (response: { data: Post }, meta, arg) => response.data,
// Pick out errors and prevent nested properties in a hook or selector
transformErrorResponse: (response: { status: string | number }, meta, arg) => response.status,
transformErrorResponse: (
response: { status: string | number },
meta,
arg
) => response.status,
invalidatesTags: ['Post'],
// onQueryStarted is useful for optimistic updates
// The 2nd parameter is the destructured `MutationLifecycleApi`
Expand Down Expand Up @@ -177,7 +181,7 @@ When using `fixedCacheKey`, the `originalArgs` property is not able to be shared

This is a modified version of the complete example you can see at the bottom of the page to highlight the `updatePost` mutation. In this scenario, a post is fetched with `useQuery`, and then an `EditablePostName` component is rendered that allows us to edit the name of the post.

```tsx title="src/features/posts/PostDetail.tsx"
```tsx no-transpile title="src/features/posts/PostDetail.tsx"
export const PostDetail = () => {
const { id } = useParams<{ id: any }>()

Expand Down
38 changes: 19 additions & 19 deletions docs/rtk-query/usage/pagination.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,36 +44,35 @@ export const { useListPostsQuery } = api

### Trigger the next page by incrementing the `page` state variable

```tsx title="src/features/posts/PostsManager.tsx"
```tsx no-transpile title="src/features/posts/PostsManager.tsx"
const PostList = () => {
const [page, setPage] = useState(1);
const { data: posts, isLoading, isFetching } = useListPostsQuery(page);
const [page, setPage] = useState(1)
const { data: posts, isLoading, isFetching } = useListPostsQuery(page)

if (isLoading) {
return <div>Loading</div>;
return <div>Loading</div>
}

if (!posts?.data) {
return <div>No posts :(</div>;
return <div>No posts :(</div>
}

return (
<div>
{posts.data.map(({ id, title, status }) => (
<div key={id}>{title} - {status}</div>
))}
<button onClick={() => setPage(page - 1)} isLoading={isFetching}>
Previous
</button>
<button
onClick={() => setPage(page + 1)}
isLoading={isFetching}
>
Next
</button>
{posts.data.map(({ id, title, status }) => (
<div key={id}>
{title} - {status}
</div>
))}
<button onClick={() => setPage(page - 1)} isLoading={isFetching}>
Previous
</button>
<button onClick={() => setPage(page + 1)} isLoading={isFetching}>
Next
</button>
</div>
);
};
)
}
```

### Automated Re-fetching of Paginated Queries
Expand Down Expand Up @@ -149,6 +148,7 @@ export const postApi = createApi({
}),
})
```

## General Pagination Example

In the following example, you'll see `Loading` on the initial query, but then as you move forward we'll use the next/previous buttons as a _fetching_ indicator while any non-cached query is performed. When you go back, the cached data will be served instantaneously.
Expand Down
Loading

0 comments on commit 417d3de

Please sign in to comment.