Skip to content

Commit

Permalink
Merge branch 'master' into create-slice-creators
Browse files Browse the repository at this point in the history
  • Loading branch information
EskiMojo14 committed Nov 29, 2024
2 parents ef2d024 + e848a55 commit 2d7def1
Show file tree
Hide file tree
Showing 83 changed files with 2,718 additions and 572 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:

# Read existing version, reuse that, add a Git short hash
- name: Set build version to Git commit
run: node scripts/writeGitVersion.mjs $(git rev-parse --short HEAD)
run: yarn tsx scripts/writeGitVersion.mts $(git rev-parse --short HEAD)

- name: Check updated version
run: jq .version package.json
Expand Down Expand Up @@ -111,7 +111,7 @@ jobs:
fail-fast: false
matrix:
node: ['20.x']
ts: ['4.7', '4.8', '4.9', '5.0', '5.1', '5.2', '5.3', '5.4', '5.5']
ts: ['5.0', '5.1', '5.2', '5.3', '5.4', '5.5']
steps:
- name: Checkout repo
uses: actions/checkout@v4
Expand Down
6 changes: 6 additions & 0 deletions docs/rtk-query/api/createApi.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ description: 'RTK Query > API: createApi reference'

Typically, you should only have one API slice per base URL that your application needs to communicate with. For example, if your site fetches data from both `/api/posts` and `/api/users`, you would have a single API slice with `/api/` as the base URL, and separate endpoint definitions for `posts` and `users`. This allows you to effectively take advantage of [automated re-fetching](../usage/automated-refetching.mdx) by defining [tag](../usage/automated-refetching.mdx#tags) relationships across endpoints.

This is because:

- Automatic tag invalidation only works within a single API slice. If you have multiple API slices, the automatic invalidation won't work across them.
- Every `createApi` call generates its own middleware, and each middleware added to the store will run checks against every dispatched action. That has a perf cost that adds up. So, if you called `createApi` 10 times and added 10 separate API middleware to the store, that will be noticeably slower perf-wise.

For maintainability purposes, you may wish to split up endpoint definitions across multiple files, while still maintaining a single API slice which includes all of these endpoints. See [code splitting](../usage/code-splitting.mdx) for how you can use the `injectEndpoints` property to inject API endpoints from other files into a single API slice definition.

:::
Expand Down Expand Up @@ -91,6 +96,7 @@ export const { useGetPokemonByNameQuery } = pokemonApi
- `endpoint` - The name of the endpoint.
- `type` - Type of request (`query` or `mutation`).
- `forced` - Indicates if a query has been forced.
- `queryCacheKey`- The computed query cache key.
- `extraOptions` - The value of the optional `extraOptions` property provided for a given endpoint

#### baseQuery function signature
Expand Down
199 changes: 143 additions & 56 deletions docs/rtk-query/api/created-api/api-slice-utils.mdx

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/rtk-query/api/created-api/hooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ type UseMutationResult<T> = {
data?: T // Returned result if present
error?: unknown // Error result if present
endpointName?: string // The name of the given endpoint for the mutation
fulfilledTimestamp?: number // Timestamp for when the mutation was completed
fulfilledTimeStamp?: number // Timestamp for when the mutation was completed

// Derived request status booleans
isUninitialized: boolean // Mutation has not been fired yet
Expand Down
5 changes: 5 additions & 0 deletions docs/rtk-query/api/created-api/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ This section documents the contents of that API structure, with the different fi

Typically, you should only have one API slice per base URL that your application needs to communicate with. For example, if your site fetches data from both `/api/posts` and `/api/users`, you would have a single API slice with `/api/` as the base URL, and separate endpoint definitions for `posts` and `users`. This allows you to effectively take advantage of [automated re-fetching](../../usage/automated-refetching.mdx) by defining [tag](../../usage/automated-refetching.mdx#tags) relationships across endpoints.

This is because:

- Automatic tag invalidation only works within a single API slice. If you have multiple API slices, the automatic invalidation won't work across them.
- Every `createApi` call generates its own middleware, and each middleware added to the store will run checks against every dispatched action. That has a perf cost that adds up. So, if you called `createApi` 10 times and added 10 separate API middleware to the store, that will be noticeably slower perf-wise.

For maintainability purposes, you may wish to split up endpoint definitions across multiple files, while still maintaining a single API slice which includes all of these endpoints. See [code splitting](../../usage/code-splitting.mdx) for how you can use the `injectEndpoints` property to inject API endpoints from other files into a single API slice definition.

:::
Expand Down
70 changes: 64 additions & 6 deletions docs/rtk-query/api/fetchBaseQuery.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ type FetchBaseQueryArgs = {
api: Pick<
BaseQueryApi,
'getState' | 'extra' | 'endpoint' | 'type' | 'forced'
>,
> & { arg: string | FetchArgs },
) => MaybePromise<Headers | void>
fetchFn?: (
input: RequestInfo,
Expand All @@ -83,14 +83,60 @@ type FetchBaseQueryResult = Promise<
meta?: { request: Request; response: Response }
}
| {
error: {
status: number
data: any
}
error: FetchBaseQueryError
data?: undefined
meta?: { request: Request; response: Response }
}
>

type FetchBaseQueryError =
| {
/**
* * `number`:
* HTTP status code
*/
status: number
data: unknown
}
| {
/**
* * `"FETCH_ERROR"`:
* An error that occurred during execution of `fetch` or the `fetchFn` callback option
**/
status: 'FETCH_ERROR'
data?: undefined
error: string
}
| {
/**
* * `"PARSING_ERROR"`:
* An error happened during parsing.
* Most likely a non-JSON-response was returned with the default `responseHandler` "JSON",
* or an error occurred while executing a custom `responseHandler`.
**/
status: 'PARSING_ERROR'
originalStatus: number
data: string
error: string
}
| {
/**
* * `"TIMEOUT_ERROR"`:
* Request timed out
**/
status: 'TIMEOUT_ERROR'
data?: undefined
error: string
}
| {
/**
* * `"CUSTOM_ERROR"`:
* A custom error type that you can return from your `queryFn` where another error might not make sense.
**/
status: 'CUSTOM_ERROR'
data?: unknown
error: string
}
```
## Parameters
Expand All @@ -105,7 +151,7 @@ Typically a string like `https://api.your-really-great-app.com/v1/`. If you don'

_(optional)_

Allows you to inject headers on every request. You can specify headers at the endpoint level, but you'll typically want to set common headers like `authorization` here. As a convenience mechanism, the second argument allows you to use `getState` to access your redux store in the event you store information you'll need there such as an auth token. Additionally, it provides access to `extra`, `endpoint`, `type`, and `forced` to unlock more granular conditional behaviors.
Allows you to inject headers on every request. You can specify headers at the endpoint level, but you'll typically want to set common headers like `authorization` here. As a convenience mechanism, the second argument allows you to use `getState` to access your redux store in the event you store information you'll need there such as an auth token. Additionally, it provides access to `arg`, `extra`, `endpoint`, `type`, and `forced` to unlock more granular conditional behaviors.

You can mutate the `headers` argument directly, and returning it is optional.

Expand All @@ -114,6 +160,7 @@ type prepareHeaders = (
headers: Headers,
api: {
getState: () => unknown
arg: string | FetchArgs
extra: unknown
endpoint: string
type: 'query' | 'mutation'
Expand Down Expand Up @@ -297,6 +344,17 @@ export const customApi = createApi({
If you make a `json` request to an API that only returns a `200` with an undefined body, `fetchBaseQuery` will pass that through as `undefined` and will not try to parse it as `json`. This can be common with some APIs, especially on `delete` requests.
:::

#### Default response handler

The default response handler is `"json"`, which is equivalent to the following function:

```ts title="Default responseHandler"
const defaultResponseHandler = async (res: Response) => {
const text = await res.text()
return text.length ? JSON.parse(text) : null
}
```

### Handling non-standard Response status codes

By default, `fetchBaseQuery` will `reject` any `Response` that does not have a status code of `2xx` and set it to `error`. This is the same behavior you've most likely experienced with `axios` and other popular libraries. In the event that you have a non-standard API you're dealing with, you can use the `validateStatus` option to customize this behavior.
Expand Down
32 changes: 16 additions & 16 deletions docs/rtk-query/internal/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ RTK-Query uses a very familiar redux-centric architecture. Where the `api` is a
The slices built inside this "build" are:
_Some of which have their own actions_

- querySlice
- mutationSlice
- invalidationSlice
- subscriptionSlice (used as a dummy slice to generate actions internally)
- internalSubscriptionsSlice
- configSlice (internal tracking of focus state, online state, hydration etc)
- `querySlice`
- `mutationSlice`
- `invalidationSlice`
- `subscriptionSlice` (used as a dummy slice to generate actions internally)
- `internalSubscriptionsSlice`
- `configSlice` (internal tracking of focus state, online state, hydration etc)

buildSlice also exposes the core action `resetApiState` which is subsequently added to the `api.util`

Expand All @@ -53,21 +53,21 @@ RTK-Query has a series of custom middlewares established within its store to han

Each middleware built during this step is referred to internally as a "Handler" and are as follows:

- `buildDevCheckHandler
- `buildCacheCollectionHandler
- `buildInvalidationByTagsHandler
- `buildPollingHandler
- `buildCacheLifecycleHandler
- `buildQueryLifecycleHandler
- `buildDevCheckHandler`
- `buildCacheCollectionHandler`
- `buildInvalidationByTagsHandler`
- `buildPollingHandler`
- `buildCacheLifecycleHandler`
- `buildQueryLifecycleHandler`

### buildSelectors

build selectors is a crucial step that exposes to the `api` and utils:

- `buildQuerySelector
- `buildMutationSelector
- `selectInvalidatedBy
- `selectCachedArgsForQuery
- `buildQuerySelector`
- `buildMutationSelector`
- `selectInvalidatedBy`
- `selectCachedArgsForQuery`

### return

Expand Down
2 changes: 1 addition & 1 deletion docs/rtk-query/usage/customizing-queries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ const staggeredBaseQueryWithBailOut = retry(
// bail out of re-tries immediately if unauthorized,
// because we know successive re-retries would be redundant
if (result.error?.status === 401) {
retry.fail(result.error)
retry.fail(result.error, result.meta)
}

return result
Expand Down
2 changes: 1 addition & 1 deletion docs/usage/immer-reducers.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ Writing immutable update logic by hand _is_ hard, and **accidentally mutating st
Immer provides a function called `produce`, which accepts two arguments: your original `state`, and a callback function. The callback function is given a "draft" version of that state, and inside the callback, it is safe to write code that mutates the draft value. Immer tracks all attempts to mutate the draft value and then replays those mutations using their immutable equivalents to create a safe, immutably updated result:

```js
import produce from 'immer'
import { produce } from 'immer'

const baseState = [
{
Expand Down
4 changes: 2 additions & 2 deletions examples/publish-ci/react-native/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9632,11 +9632,11 @@ __metadata:

"typescript@patch:typescript@npm%3A^5.5.4#optional!builtin<compat/typescript>":
version: 5.5.4
resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin<compat/typescript>::version=5.5.4&hash=d69c25"
resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin<compat/typescript>::version=5.5.4&hash=379a07"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10/2c065f0ef81855eac25c9b658a3c9da65ffc005260c12854c2286f40f3667e1b1ecf8bdbdd37b59aa0397920378ce7900bff8cb32e0f1c7af6fd86efc676718c
checksum: 10/746fdd0865c5ce4f15e494c57ede03a9e12ede59cfdb40da3a281807853fe63b00ef1c912d7222143499aa82f18b8b472baa1830df8804746d09b55f6cf5b1cc
languageName: node
linkType: hard

Expand Down
3 changes: 2 additions & 1 deletion examples/query/react/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"react-scripts": "5.0.1"
},
"devDependencies": {
"@testing-library/react": "^13.3.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.0.1",
"@types/jest": "^26.0.23",
"@types/react": "^18.0.5",
"@types/react-dom": "^18.0.5",
Expand Down
3 changes: 2 additions & 1 deletion examples/query/react/kitchen-sink/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
"react-scripts": "5.0.1"
},
"devDependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^5.11.5",
"@testing-library/react": "^13.3.0",
"@testing-library/react": "^16.0.1",
"@types/jest": "^26.0.23",
"@types/node": "^14.14.6",
"@types/react": "^18.0.5",
Expand Down
2 changes: 1 addition & 1 deletion packages/rtk-query-codegen-openapi/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rtk-query/codegen-openapi",
"version": "2.0.0-alpha.1",
"version": "2.0.0",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"module": "lib/index.mjs",
Expand Down
47 changes: 32 additions & 15 deletions packages/rtk-query-codegen-openapi/src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ export async function generateApi(
filterEndpoints,
endpointOverrides,
unionUndefined,
encodeParams = false,
encodePathParams = false,
encodeQueryParams = false,
flattenArg = false,
includeDefault = false,
useEnumType = false,
Expand Down Expand Up @@ -398,7 +399,14 @@ export async function generateApi(
type: isQuery ? 'query' : 'mutation',
Response: ResponseTypeName,
QueryArg,
queryFn: generateQueryFn({ operationDefinition, queryArg, isQuery, isFlatArg, encodeParams }),
queryFn: generateQueryFn({
operationDefinition,
queryArg,
isQuery,
isFlatArg,
encodePathParams,
encodeQueryParams,
}),
extraEndpointsProps: isQuery
? generateQueryEndpointProps({ operationDefinition })
: generateMutationEndpointProps({ operationDefinition }),
Expand All @@ -411,13 +419,15 @@ export async function generateApi(
queryArg,
isFlatArg,
isQuery,
encodeParams,
encodePathParams,
encodeQueryParams,
}: {
operationDefinition: OperationDefinition;
queryArg: QueryArgDefinitions;
isFlatArg: boolean;
isQuery: boolean;
encodeParams: boolean;
encodePathParams: boolean;
encodeQueryParams: boolean;
}) {
const { path, verb } = operationDefinition;

Expand All @@ -434,14 +444,21 @@ export async function generateApi(

const properties = parameters.map((param) => {
const value = isFlatArg ? rootObject : accessProperty(rootObject, param.name);
return createPropertyAssignment(
param.originalName,
encodeParams && param.param?.in === 'query'
? factory.createCallExpression(factory.createIdentifier('encodeURIComponent'), undefined, [
factory.createCallExpression(factory.createIdentifier('String'), undefined, [value]),
])
: value
);

const encodedValue =
encodeQueryParams && param.param?.in === 'query'
? factory.createConditionalExpression(
value,
undefined,
factory.createCallExpression(factory.createIdentifier('encodeURIComponent'), undefined, [
factory.createCallExpression(factory.createIdentifier('String'), undefined, [value]),
]),
undefined,
factory.createIdentifier('undefined')
)
: value;

return createPropertyAssignment(param.originalName, encodedValue);
});

return factory.createPropertyAssignment(
Expand All @@ -463,7 +480,7 @@ export async function generateApi(
[
factory.createPropertyAssignment(
factory.createIdentifier('url'),
generatePathExpression(path, pickParams('path'), rootObject, isFlatArg, encodeParams)
generatePathExpression(path, pickParams('path'), rootObject, isFlatArg, encodePathParams)
),
isQuery && verb.toUpperCase() === 'GET'
? undefined
Expand Down Expand Up @@ -511,7 +528,7 @@ function generatePathExpression(
pathParameters: QueryArgDefinition[],
rootObject: ts.Identifier,
isFlatArg: boolean,
encodeParams: boolean
encodePathParams: boolean
) {
const expressions: Array<[string, string]> = [];

Expand All @@ -529,7 +546,7 @@ function generatePathExpression(
factory.createTemplateHead(head),
expressions.map(([prop, literal], index) => {
const value = isFlatArg ? rootObject : accessProperty(rootObject, prop);
const encodedValue = encodeParams
const encodedValue = encodePathParams
? factory.createCallExpression(factory.createIdentifier('encodeURIComponent'), undefined, [
factory.createCallExpression(factory.createIdentifier('String'), undefined, [value]),
])
Expand Down
9 changes: 7 additions & 2 deletions packages/rtk-query-codegen-openapi/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,14 @@ export interface CommonOptions {
tag?: boolean;
/**
* defaults to false
* `true` will add `encodeURIComponent` to the generated query params
* `true` will add `encodeURIComponent` to the generated path parameters
*/
encodeParams?: boolean;
encodePathParams?: boolean;
/**
* defaults to false
* `true` will add `encodeURIComponent` to the generated query parameters
*/
encodeQueryParams?: boolean;
/**
* defaults to false
* `true` will "flatten" the arg so that you can do things like `useGetEntityById(1)` instead of `useGetEntityById({ entityId: 1 })`
Expand Down
Loading

0 comments on commit 2d7def1

Please sign in to comment.