Add settings page for managing forecasting API key and URL via UI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-20 14:37:36 +00:00
parent cae411eae7
commit 1c411e402e
19809 changed files with 1962608 additions and 97 deletions

21
frontend/node_modules/@reduxjs/toolkit/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Mark Erikson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

112
frontend/node_modules/@reduxjs/toolkit/README.md generated vendored Normal file
View file

@ -0,0 +1,112 @@
# Redux Toolkit
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/reduxjs/redux-toolkit/tests.yml?style=flat-square)
[![npm version](https://img.shields.io/npm/v/@reduxjs/toolkit.svg?style=flat-square)](https://www.npmjs.com/package/@reduxjs/toolkit)
[![npm downloads](https://img.shields.io/npm/dm/@reduxjs/toolkit.svg?style=flat-square&label=RTK+downloads)](https://www.npmjs.com/package/@reduxjs/toolkit)
**The official, opinionated, batteries-included toolset for efficient Redux development**
## Installation
### Create a React Redux App
The recommended way to start new apps with React and Redux Toolkit is by using [our official Redux Toolkit + TS template for Vite](https://github.com/reduxjs/redux-templates), or by creating a new Next.js project using [Next's `with-redux` template](https://github.com/vercel/next.js/tree/canary/examples/with-redux).
Both of these already have Redux Toolkit and React-Redux configured appropriately for that build tool, and come with a small example app that demonstrates how to use several of Redux Toolkit's features.
```bash
# Vite with our Redux+TS template
# (using the `degit` tool to clone and extract the template)
npx degit reduxjs/redux-templates/packages/vite-template-redux my-app
# Next.js using the `with-redux` template
npx create-next-app --example with-redux my-app
```
We do not currently have official React Native templates, but recommend these templates for standard React Native and for Expo:
- https://github.com/rahsheen/react-native-template-redux-typescript
- https://github.com/rahsheen/expo-template-redux-typescript
### An Existing App
Redux Toolkit is available as a package on NPM for use with a module bundler or in a Node application:
```bash
# NPM
npm install @reduxjs/toolkit
# Yarn
yarn add @reduxjs/toolkit
```
If you use an AI agent, run `npx @tanstack/intent@latest install` to install agent skills.
The package includes a precompiled ESM build that can be used as a [`<script type="module">` tag](https://unpkg.com/@reduxjs/toolkit/dist/redux-toolkit.browser.mjs) directly in the browser.
## Documentation
The Redux Toolkit docs are available at **https://redux-toolkit.js.org**, including API references and usage guides for all of the APIs included in Redux Toolkit.
The Redux core docs at https://redux.js.org includes the full Redux tutorials, as well usage guides on general Redux patterns.
## Purpose
The **Redux Toolkit** package is intended to be the standard way to write Redux logic. It was originally created to help address three common concerns about Redux:
- "Configuring a Redux store is too complicated"
- "I have to add a lot of packages to get Redux to do anything useful"
- "Redux requires too much boilerplate code"
We can't solve every use case, but in the spirit of [`create-react-app`](https://github.com/facebook/create-react-app), we can try to provide some tools that abstract over the setup process and handle the most common use cases, as well as include some useful utilities that will let the user simplify their application code.
Because of that, this package is deliberately limited in scope. It does _not_ address concepts like "reusable encapsulated Redux modules", folder or file structures, managing entity relationships in the store, and so on.
Redux Toolkit also includes a powerful data fetching and caching capability that we've dubbed "RTK Query". It's included in the package as a separate set of entry points. It's optional, but can eliminate the need to hand-write data fetching logic yourself.
## What's Included
Redux Toolkit includes these APIs:
- `configureStore()`: wraps `createStore` to provide simplified configuration options and good defaults. It can automatically combine your slice reducers, add whatever Redux middleware you supply, includes `redux-thunk` by default, and enables use of the Redux DevTools Extension.
- `createReducer()`: lets you supply a lookup table of action types to case reducer functions, rather than writing switch statements. In addition, it automatically uses the [`immer` library](https://github.com/mweststrate/immer) to let you write simpler immutable updates with normal mutative code, like `state.todos[3].completed = true`.
- `createAction()`: generates an action creator function for the given action type string. The function itself has `toString()` defined, so that it can be used in place of the type constant.
- `createSlice()`: combines `createReducer()` + `createAction()`. Accepts an object of reducer functions, a slice name, and an initial state value, and automatically generates a slice reducer with corresponding action creators and action types.
- `combineSlices()`: combines multiple slices into a single reducer, and allows "lazy loading" of slices after initialisation.
- `createListenerMiddleware()`: lets you define "listener" entries that contain an "effect" callback with additional logic, and a way to specify when that callback should run based on dispatched actions or state changes. A lightweight alternative to Redux async middleware like sagas and observables.
- `createAsyncThunk()`: accepts an action type string and a function that returns a promise, and generates a thunk that dispatches `pending/resolved/rejected` action types based on that promise
- `createEntityAdapter()`: generates a set of reusable reducers and selectors to manage normalized data in the store
- The `createSelector()` utility from the [Reselect](https://github.com/reduxjs/reselect) library, re-exported for ease of use.
For details, see [the Redux Toolkit API Reference section in the docs](https://redux-toolkit.js.org/api/configureStore).
## RTK Query
**RTK Query** is provided as an optional addon within the `@reduxjs/toolkit` package. It is purpose-built to solve the use case of data fetching and caching, supplying a compact, but powerful toolset to define an API interface layer for your app. It is intended to simplify common cases for loading data in a web application, eliminating the need to hand-write data fetching & caching logic yourself.
RTK Query is built on top of the Redux Toolkit core for its implementation, using [Redux](https://redux.js.org/) internally for its architecture. Although knowledge of Redux and RTK are not required to use RTK Query, you should explore all of the additional global store management capabilities they provide, as well as installing the [Redux DevTools browser extension](https://github.com/reduxjs/redux-devtools), which works flawlessly with RTK Query to traverse and replay a timeline of your request & cache behavior.
RTK Query is included within the installation of the core Redux Toolkit package. It is available via either of the two entry points below:
```ts no-transpile
import { createApi } from '@reduxjs/toolkit/query'
/* React-specific entry point that automatically generates
hooks corresponding to the defined endpoints */
import { createApi } from '@reduxjs/toolkit/query/react'
```
### What's included
RTK Query includes these APIs:
- `createApi()`: The core of RTK Query's functionality. It allows you to define a set of endpoints describe how to retrieve data from a series of endpoints, including configuration of how to fetch and transform that data. In most cases, you should use this once per app, with "one API slice per base URL" as a rule of thumb.
- `fetchBaseQuery()`: A small wrapper around fetch that aims to simplify requests. Intended as the recommended baseQuery to be used in createApi for the majority of users.
- `<ApiProvider />`: Can be used as a Provider if you do not already have a Redux store.
- `setupListeners()`: A utility used to enable refetchOnMount and refetchOnReconnect behaviors.
See the [**RTK Query Overview**](https://redux-toolkit.js.org/rtk-query/overview) page for more details on what RTK Query is, what problems it solves, and how to use it.
## Contributing
Please refer to our [contributing guide](/CONTRIBUTING.md) to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to Redux Toolkit.

View file

@ -0,0 +1,6 @@
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./redux-toolkit.production.min.cjs')
} else {
module.exports = require('./redux-toolkit.development.cjs')
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2665
frontend/node_modules/@reduxjs/toolkit/dist/index.d.mts generated vendored Normal file

File diff suppressed because it is too large Load diff

2665
frontend/node_modules/@reduxjs/toolkit/dist/index.d.ts generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,6 @@
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./rtk-query.production.min.cjs')
} else {
module.exports = require('./rtk-query.development.cjs')
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,6 @@
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./rtk-query-react.production.min.cjs')
} else {
module.exports = require('./rtk-query-react.development.cjs')
}

View file

@ -0,0 +1,748 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/query/react/index.ts
var react_exports = {};
__export(react_exports, {
ApiProvider: () => ApiProvider,
UNINITIALIZED_VALUE: () => UNINITIALIZED_VALUE,
createApi: () => createApi,
reactHooksModule: () => reactHooksModule,
reactHooksModuleName: () => reactHooksModuleName
});
module.exports = __toCommonJS(react_exports);
// src/query/react/rtkqImports.ts
var import_query = require("@reduxjs/toolkit/query");
// src/query/react/module.ts
var import_toolkit2 = require("@reduxjs/toolkit");
var import_react_redux2 = require("react-redux");
var import_reselect = require("reselect");
// src/query/utils/capitalize.ts
function capitalize(str) {
return str.replace(str[0], str[0].toUpperCase());
}
// src/query/utils/countObjectKeys.ts
function countObjectKeys(obj) {
let count = 0;
for (const _key in obj) {
count++;
}
return count;
}
// src/query/endpointDefinitions.ts
var ENDPOINT_QUERY = "query" /* query */;
var ENDPOINT_MUTATION = "mutation" /* mutation */;
var ENDPOINT_INFINITEQUERY = "infinitequery" /* infinitequery */;
function isQueryDefinition(e) {
return e.type === ENDPOINT_QUERY;
}
function isMutationDefinition(e) {
return e.type === ENDPOINT_MUTATION;
}
function isInfiniteQueryDefinition(e) {
return e.type === ENDPOINT_INFINITEQUERY;
}
// src/query/tsHelpers.ts
function safeAssign(target, ...args) {
return Object.assign(target, ...args);
}
// src/query/react/buildHooks.ts
var import_toolkit = require("@reduxjs/toolkit");
// src/query/react/constants.ts
var UNINITIALIZED_VALUE = /* @__PURE__ */ Symbol();
// src/query/react/reactImports.ts
var import_react = require("react");
// src/query/react/reactReduxImports.ts
var import_react_redux = require("react-redux");
// src/query/react/useSerializedStableValue.ts
function useStableQueryArgs(queryArgs) {
const cache = (0, import_react.useRef)(queryArgs);
const copy = (0, import_react.useMemo)(() => (0, import_query.copyWithStructuralSharing)(cache.current, queryArgs), [queryArgs]);
(0, import_react.useEffect)(() => {
if (cache.current !== copy) {
cache.current = copy;
}
}, [copy]);
return copy;
}
// src/query/react/useShallowStableValue.ts
function useShallowStableValue(value) {
const cache = (0, import_react.useRef)(value);
(0, import_react.useEffect)(() => {
if (!(0, import_react_redux.shallowEqual)(cache.current, value)) {
cache.current = value;
}
}, [value]);
return (0, import_react_redux.shallowEqual)(cache.current, value) ? cache.current : value;
}
// src/query/react/buildHooks.ts
var canUseDOM = () => !!(typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined");
var isDOM = /* @__PURE__ */ canUseDOM();
var isRunningInReactNative = () => typeof navigator !== "undefined" && navigator.product === "ReactNative";
var isReactNative = /* @__PURE__ */ isRunningInReactNative();
var getUseIsomorphicLayoutEffect = () => isDOM || isReactNative ? import_react.useLayoutEffect : import_react.useEffect;
var useIsomorphicLayoutEffect = /* @__PURE__ */ getUseIsomorphicLayoutEffect();
var noPendingQueryStateSelector = (selected) => {
if (selected.isUninitialized) {
return {
...selected,
isUninitialized: false,
isFetching: true,
isLoading: selected.data !== void 0 ? false : true,
// This is the one place where we still have to use `QueryStatus` as an enum,
// since it's the only reference in the React package and not in the core.
status: import_query.QueryStatus.pending
};
}
return selected;
};
function pick(obj, ...keys) {
const ret = {};
keys.forEach((key) => {
ret[key] = obj[key];
});
return ret;
}
var COMMON_HOOK_DEBUG_FIELDS = ["data", "status", "isLoading", "isSuccess", "isError", "error"];
function buildHooks({
api,
moduleOptions: {
batch,
hooks: {
useDispatch,
useSelector,
useStore
},
unstable__sideEffectsInRender,
createSelector
},
serializeQueryArgs,
context
}) {
const usePossiblyImmediateEffect = unstable__sideEffectsInRender ? (cb) => cb() : import_react.useEffect;
const unsubscribePromiseRef = (ref) => ref.current?.unsubscribe?.();
const endpointDefinitions = context.endpointDefinitions;
return {
buildQueryHooks,
buildInfiniteQueryHooks,
buildMutationHook,
usePrefetch
};
function queryStatePreSelector(currentState, lastResult, queryArgs) {
if (lastResult?.endpointName && currentState.isUninitialized) {
const {
endpointName
} = lastResult;
const endpointDefinition = endpointDefinitions[endpointName];
if (queryArgs !== import_query.skipToken && serializeQueryArgs({
queryArgs: lastResult.originalArgs,
endpointDefinition,
endpointName
}) === serializeQueryArgs({
queryArgs,
endpointDefinition,
endpointName
})) lastResult = void 0;
}
let data = currentState.isSuccess ? currentState.data : lastResult?.data;
if (data === void 0) data = currentState.data;
const hasData = data !== void 0;
const isFetching = currentState.isLoading;
const isLoading = (!lastResult || lastResult.isLoading || lastResult.isUninitialized) && !hasData && isFetching;
const isSuccess = currentState.isSuccess || hasData && (isFetching && !lastResult?.isError || currentState.isUninitialized);
return {
...currentState,
data,
currentData: currentState.data,
isFetching,
isLoading,
isSuccess
};
}
function infiniteQueryStatePreSelector(currentState, lastResult, queryArgs) {
if (lastResult?.endpointName && currentState.isUninitialized) {
const {
endpointName
} = lastResult;
const endpointDefinition = endpointDefinitions[endpointName];
if (queryArgs !== import_query.skipToken && serializeQueryArgs({
queryArgs: lastResult.originalArgs,
endpointDefinition,
endpointName
}) === serializeQueryArgs({
queryArgs,
endpointDefinition,
endpointName
})) lastResult = void 0;
}
let data = currentState.isSuccess ? currentState.data : lastResult?.data;
if (data === void 0) data = currentState.data;
const hasData = data !== void 0;
const isFetching = currentState.isLoading;
const isLoading = (!lastResult || lastResult.isLoading || lastResult.isUninitialized) && !hasData && isFetching;
const isSuccess = currentState.isSuccess || hasData && (isFetching && !lastResult?.isError || currentState.isUninitialized);
return {
...currentState,
data,
currentData: currentState.data,
isFetching,
isLoading,
isSuccess
};
}
function usePrefetch(endpointName, defaultOptions) {
const dispatch = useDispatch();
const stableDefaultOptions = useShallowStableValue(defaultOptions);
return (0, import_react.useCallback)((arg, options) => dispatch(api.util.prefetch(endpointName, arg, {
...stableDefaultOptions,
...options
})), [endpointName, dispatch, stableDefaultOptions]);
}
function useQuerySubscriptionCommonImpl(endpointName, arg, {
refetchOnReconnect,
refetchOnFocus,
refetchOnMountOrArgChange,
skip = false,
pollingInterval = 0,
skipPollingIfUnfocused = false,
...rest
} = {}) {
const {
initiate
} = api.endpoints[endpointName];
const dispatch = useDispatch();
const subscriptionSelectorsRef = (0, import_react.useRef)(void 0);
if (!subscriptionSelectorsRef.current) {
const returnedValue = dispatch(api.internalActions.internal_getRTKQSubscriptions());
if (true) {
if (typeof returnedValue !== "object" || typeof returnedValue?.type === "string") {
throw new Error(false ? _formatProdErrorMessage(37) : `Warning: Middleware for RTK-Query API at reducerPath "${api.reducerPath}" has not been added to the store.
You must add the middleware for RTK-Query to function correctly!`);
}
}
subscriptionSelectorsRef.current = returnedValue;
}
const stableArg = useStableQueryArgs(skip ? import_query.skipToken : arg);
const stableSubscriptionOptions = useShallowStableValue({
refetchOnReconnect,
refetchOnFocus,
pollingInterval,
skipPollingIfUnfocused
});
const initialPageParam = rest.initialPageParam;
const stableInitialPageParam = useShallowStableValue(initialPageParam);
const refetchCachedPages = rest.refetchCachedPages;
const stableRefetchCachedPages = useShallowStableValue(refetchCachedPages);
const promiseRef = (0, import_react.useRef)(void 0);
let {
queryCacheKey,
requestId
} = promiseRef.current || {};
let currentRenderHasSubscription = false;
if (queryCacheKey && requestId) {
currentRenderHasSubscription = subscriptionSelectorsRef.current.isRequestSubscribed(queryCacheKey, requestId);
}
const subscriptionRemoved = !currentRenderHasSubscription && promiseRef.current !== void 0;
usePossiblyImmediateEffect(() => {
if (subscriptionRemoved) {
promiseRef.current = void 0;
}
}, [subscriptionRemoved]);
usePossiblyImmediateEffect(() => {
const lastPromise = promiseRef.current;
if (typeof process !== "undefined" && false) {
console.log(subscriptionRemoved);
}
if (stableArg === import_query.skipToken) {
lastPromise?.unsubscribe();
promiseRef.current = void 0;
return;
}
const lastSubscriptionOptions = promiseRef.current?.subscriptionOptions;
if (!lastPromise || lastPromise.arg !== stableArg) {
lastPromise?.unsubscribe();
const promise = dispatch(initiate(stableArg, {
subscriptionOptions: stableSubscriptionOptions,
forceRefetch: refetchOnMountOrArgChange,
...isInfiniteQueryDefinition(endpointDefinitions[endpointName]) ? {
initialPageParam: stableInitialPageParam,
refetchCachedPages: stableRefetchCachedPages
} : {}
}));
promiseRef.current = promise;
} else if (stableSubscriptionOptions !== lastSubscriptionOptions) {
lastPromise.updateSubscriptionOptions(stableSubscriptionOptions);
}
}, [dispatch, initiate, refetchOnMountOrArgChange, stableArg, stableSubscriptionOptions, subscriptionRemoved, stableInitialPageParam, stableRefetchCachedPages, endpointName]);
return [promiseRef, dispatch, initiate, stableSubscriptionOptions];
}
function buildUseQueryState(endpointName, preSelector) {
const useQueryState = (arg, {
skip = false,
selectFromResult
} = {}) => {
const {
select
} = api.endpoints[endpointName];
const stableArg = useStableQueryArgs(skip ? import_query.skipToken : arg);
const lastValue = (0, import_react.useRef)(void 0);
const selectDefaultResult = (0, import_react.useMemo)(() => (
// Normally ts-ignores are bad and should be avoided, but we're
// already casting this selector to be `Selector<any>` anyway,
// so the inconsistencies don't matter here
// @ts-ignore
createSelector([
// @ts-ignore
select(stableArg),
(_, lastResult) => lastResult,
(_) => stableArg
], preSelector, {
memoizeOptions: {
resultEqualityCheck: import_react_redux.shallowEqual
}
})
), [select, stableArg]);
const querySelector = (0, import_react.useMemo)(() => selectFromResult ? createSelector([selectDefaultResult], selectFromResult, {
devModeChecks: {
identityFunctionCheck: "never"
}
}) : selectDefaultResult, [selectDefaultResult, selectFromResult]);
const currentState = useSelector((state) => querySelector(state, lastValue.current), import_react_redux.shallowEqual);
const store = useStore();
const newLastValue = selectDefaultResult(store.getState(), lastValue.current);
useIsomorphicLayoutEffect(() => {
lastValue.current = newLastValue;
}, [newLastValue]);
return currentState;
};
return useQueryState;
}
function usePromiseRefUnsubscribeOnUnmount(promiseRef) {
(0, import_react.useEffect)(() => {
return () => {
unsubscribePromiseRef(promiseRef);
promiseRef.current = void 0;
};
}, [promiseRef]);
}
function refetchOrErrorIfUnmounted(promiseRef) {
if (!promiseRef.current) throw new Error(false ? _formatProdErrorMessage2(38) : "Cannot refetch a query that has not been started yet.");
return promiseRef.current.refetch();
}
function buildQueryHooks(endpointName) {
const useQuerySubscription = (arg, options = {}) => {
const [promiseRef] = useQuerySubscriptionCommonImpl(endpointName, arg, options);
usePromiseRefUnsubscribeOnUnmount(promiseRef);
return (0, import_react.useMemo)(() => ({
/**
* A method to manually refetch data for the query
*/
refetch: () => refetchOrErrorIfUnmounted(promiseRef)
}), [promiseRef]);
};
const useLazyQuerySubscription = ({
refetchOnReconnect,
refetchOnFocus,
pollingInterval = 0,
skipPollingIfUnfocused = false
} = {}) => {
const {
initiate
} = api.endpoints[endpointName];
const dispatch = useDispatch();
const [arg, setArg] = (0, import_react.useState)(UNINITIALIZED_VALUE);
const promiseRef = (0, import_react.useRef)(void 0);
const stableSubscriptionOptions = useShallowStableValue({
refetchOnReconnect,
refetchOnFocus,
pollingInterval,
skipPollingIfUnfocused
});
usePossiblyImmediateEffect(() => {
const lastSubscriptionOptions = promiseRef.current?.subscriptionOptions;
if (stableSubscriptionOptions !== lastSubscriptionOptions) {
promiseRef.current?.updateSubscriptionOptions(stableSubscriptionOptions);
}
}, [stableSubscriptionOptions]);
const subscriptionOptionsRef = (0, import_react.useRef)(stableSubscriptionOptions);
usePossiblyImmediateEffect(() => {
subscriptionOptionsRef.current = stableSubscriptionOptions;
}, [stableSubscriptionOptions]);
const trigger = (0, import_react.useCallback)(function(arg2, preferCacheValue = false) {
let promise;
batch(() => {
unsubscribePromiseRef(promiseRef);
promiseRef.current = promise = dispatch(initiate(arg2, {
subscriptionOptions: subscriptionOptionsRef.current,
forceRefetch: !preferCacheValue
}));
setArg(arg2);
});
return promise;
}, [dispatch, initiate]);
const reset = (0, import_react.useCallback)(() => {
if (promiseRef.current?.queryCacheKey) {
dispatch(api.internalActions.removeQueryResult({
queryCacheKey: promiseRef.current?.queryCacheKey
}));
}
}, [dispatch]);
(0, import_react.useEffect)(() => {
return () => {
unsubscribePromiseRef(promiseRef);
};
}, []);
(0, import_react.useEffect)(() => {
if (arg !== UNINITIALIZED_VALUE && !promiseRef.current) {
trigger(arg, true);
}
}, [arg, trigger]);
return (0, import_react.useMemo)(() => [trigger, arg, {
reset
}], [trigger, arg, reset]);
};
const useQueryState = buildUseQueryState(endpointName, queryStatePreSelector);
return {
useQueryState,
useQuerySubscription,
useLazyQuerySubscription,
useLazyQuery(options) {
const [trigger, arg, {
reset
}] = useLazyQuerySubscription(options);
const queryStateResults = useQueryState(arg, {
...options,
skip: arg === UNINITIALIZED_VALUE
});
const info = (0, import_react.useMemo)(() => ({
lastArg: arg
}), [arg]);
return (0, import_react.useMemo)(() => [trigger, {
...queryStateResults,
reset
}, info], [trigger, queryStateResults, reset, info]);
},
useQuery(arg, options) {
const querySubscriptionResults = useQuerySubscription(arg, options);
const queryStateResults = useQueryState(arg, {
selectFromResult: arg === import_query.skipToken || options?.skip ? void 0 : noPendingQueryStateSelector,
...options
});
const debugValue = pick(queryStateResults, ...COMMON_HOOK_DEBUG_FIELDS);
(0, import_react.useDebugValue)(debugValue);
return (0, import_react.useMemo)(() => ({
...queryStateResults,
...querySubscriptionResults
}), [queryStateResults, querySubscriptionResults]);
}
};
}
function buildInfiniteQueryHooks(endpointName) {
const useInfiniteQuerySubscription = (arg, options = {}) => {
const [promiseRef, dispatch, initiate, stableSubscriptionOptions] = useQuerySubscriptionCommonImpl(endpointName, arg, options);
const subscriptionOptionsRef = (0, import_react.useRef)(stableSubscriptionOptions);
usePossiblyImmediateEffect(() => {
subscriptionOptionsRef.current = stableSubscriptionOptions;
}, [stableSubscriptionOptions]);
const hookRefetchCachedPages = options.refetchCachedPages;
const stableHookRefetchCachedPages = useShallowStableValue(hookRefetchCachedPages);
const trigger = (0, import_react.useCallback)(function(arg2, direction) {
let promise;
batch(() => {
unsubscribePromiseRef(promiseRef);
promiseRef.current = promise = dispatch(initiate(arg2, {
subscriptionOptions: subscriptionOptionsRef.current,
direction
}));
});
return promise;
}, [promiseRef, dispatch, initiate]);
usePromiseRefUnsubscribeOnUnmount(promiseRef);
const stableArg = useStableQueryArgs(options.skip ? import_query.skipToken : arg);
const refetch = (0, import_react.useCallback)((options2) => {
if (!promiseRef.current) throw new Error(false ? _formatProdErrorMessage3(38) : "Cannot refetch a query that has not been started yet.");
const mergedOptions = {
refetchCachedPages: options2?.refetchCachedPages ?? stableHookRefetchCachedPages
};
return promiseRef.current.refetch(mergedOptions);
}, [promiseRef, stableHookRefetchCachedPages]);
return (0, import_react.useMemo)(() => {
const fetchNextPage = () => {
return trigger(stableArg, "forward");
};
const fetchPreviousPage = () => {
return trigger(stableArg, "backward");
};
return {
trigger,
/**
* A method to manually refetch data for the query
*/
refetch,
fetchNextPage,
fetchPreviousPage
};
}, [refetch, trigger, stableArg]);
};
const useInfiniteQueryState = buildUseQueryState(endpointName, infiniteQueryStatePreSelector);
return {
useInfiniteQueryState,
useInfiniteQuerySubscription,
useInfiniteQuery(arg, options) {
const {
refetch,
fetchNextPage,
fetchPreviousPage
} = useInfiniteQuerySubscription(arg, options);
const queryStateResults = useInfiniteQueryState(arg, {
selectFromResult: arg === import_query.skipToken || options?.skip ? void 0 : noPendingQueryStateSelector,
...options
});
const debugValue = pick(queryStateResults, ...COMMON_HOOK_DEBUG_FIELDS, "hasNextPage", "hasPreviousPage");
(0, import_react.useDebugValue)(debugValue);
return (0, import_react.useMemo)(() => ({
...queryStateResults,
fetchNextPage,
fetchPreviousPage,
refetch
}), [queryStateResults, fetchNextPage, fetchPreviousPage, refetch]);
}
};
}
function buildMutationHook(name) {
return ({
selectFromResult,
fixedCacheKey
} = {}) => {
const {
select,
initiate
} = api.endpoints[name];
const dispatch = useDispatch();
const [promise, setPromise] = (0, import_react.useState)();
(0, import_react.useEffect)(() => () => {
if (!promise?.arg.fixedCacheKey) {
promise?.reset();
}
}, [promise]);
const triggerMutation = (0, import_react.useCallback)(function(arg) {
const promise2 = dispatch(initiate(arg, {
fixedCacheKey
}));
setPromise(promise2);
return promise2;
}, [dispatch, initiate, fixedCacheKey]);
const {
requestId
} = promise || {};
const selectDefaultResult = (0, import_react.useMemo)(() => select({
fixedCacheKey,
requestId: promise?.requestId
}), [fixedCacheKey, promise, select]);
const mutationSelector = (0, import_react.useMemo)(() => selectFromResult ? createSelector([selectDefaultResult], selectFromResult) : selectDefaultResult, [selectFromResult, selectDefaultResult]);
const currentState = useSelector(mutationSelector, import_react_redux.shallowEqual);
const originalArgs = fixedCacheKey == null ? promise?.arg.originalArgs : void 0;
const reset = (0, import_react.useCallback)(() => {
batch(() => {
if (promise) {
setPromise(void 0);
}
if (fixedCacheKey) {
dispatch(api.internalActions.removeMutationResult({
requestId,
fixedCacheKey
}));
}
});
}, [dispatch, fixedCacheKey, promise, requestId]);
const debugValue = pick(currentState, ...COMMON_HOOK_DEBUG_FIELDS, "endpointName");
(0, import_react.useDebugValue)(debugValue);
const finalState = (0, import_react.useMemo)(() => ({
...currentState,
originalArgs,
reset
}), [currentState, originalArgs, reset]);
return (0, import_react.useMemo)(() => [triggerMutation, finalState], [triggerMutation, finalState]);
};
}
}
// src/query/react/module.ts
var reactHooksModuleName = /* @__PURE__ */ Symbol();
var reactHooksModule = ({
batch = import_react_redux2.batch,
hooks = {
useDispatch: import_react_redux2.useDispatch,
useSelector: import_react_redux2.useSelector,
useStore: import_react_redux2.useStore
},
createSelector = import_reselect.createSelector,
unstable__sideEffectsInRender = false,
...rest
} = {}) => {
if (true) {
const hookNames = ["useDispatch", "useSelector", "useStore"];
let warned = false;
for (const hookName of hookNames) {
if (countObjectKeys(rest) > 0) {
if (rest[hookName]) {
if (!warned) {
console.warn("As of RTK 2.0, the hooks now need to be specified as one object, provided under a `hooks` key:\n`reactHooksModule({ hooks: { useDispatch, useSelector, useStore } })`");
warned = true;
}
}
hooks[hookName] = rest[hookName];
}
if (typeof hooks[hookName] !== "function") {
throw new Error(false ? _formatProdErrorMessage4(36) : `When using custom hooks for context, all ${hookNames.length} hooks need to be provided: ${hookNames.join(", ")}.
Hook ${hookName} was either not provided or not a function.`);
}
}
}
return {
name: reactHooksModuleName,
init(api, {
serializeQueryArgs
}, context) {
const anyApi = api;
const {
buildQueryHooks,
buildInfiniteQueryHooks,
buildMutationHook,
usePrefetch
} = buildHooks({
api,
moduleOptions: {
batch,
hooks,
unstable__sideEffectsInRender,
createSelector
},
serializeQueryArgs,
context
});
safeAssign(anyApi, {
usePrefetch
});
safeAssign(context, {
batch
});
return {
injectEndpoint(endpointName, definition) {
if (isQueryDefinition(definition)) {
const {
useQuery,
useLazyQuery,
useLazyQuerySubscription,
useQueryState,
useQuerySubscription
} = buildQueryHooks(endpointName);
safeAssign(anyApi.endpoints[endpointName], {
useQuery,
useLazyQuery,
useLazyQuerySubscription,
useQueryState,
useQuerySubscription
});
api[`use${capitalize(endpointName)}Query`] = useQuery;
api[`useLazy${capitalize(endpointName)}Query`] = useLazyQuery;
}
if (isMutationDefinition(definition)) {
const useMutation = buildMutationHook(endpointName);
safeAssign(anyApi.endpoints[endpointName], {
useMutation
});
api[`use${capitalize(endpointName)}Mutation`] = useMutation;
} else if (isInfiniteQueryDefinition(definition)) {
const {
useInfiniteQuery,
useInfiniteQuerySubscription,
useInfiniteQueryState
} = buildInfiniteQueryHooks(endpointName);
safeAssign(anyApi.endpoints[endpointName], {
useInfiniteQuery,
useInfiniteQuerySubscription,
useInfiniteQueryState
});
api[`use${capitalize(endpointName)}InfiniteQuery`] = useInfiniteQuery;
}
}
};
}
};
};
// src/query/react/index.ts
__reExport(react_exports, require("@reduxjs/toolkit/query"), module.exports);
// src/query/react/ApiProvider.tsx
var import_toolkit3 = require("@reduxjs/toolkit");
var React = __toESM(require("react"));
function ApiProvider(props) {
const context = props.context || import_react_redux.ReactReduxContext;
const existingContext = (0, import_react.useContext)(context);
if (existingContext) {
throw new Error(false ? _formatProdErrorMessage5(35) : "Existing Redux context detected. If you already have a store set up, please use the traditional Redux setup.");
}
const [store] = React.useState(() => (0, import_toolkit3.configureStore)({
reducer: {
[props.api.reducerPath]: props.api.reducer
},
middleware: (gDM) => gDM().concat(props.api.middleware)
}));
(0, import_react.useEffect)(() => props.setupListeners === false ? void 0 : (0, import_query.setupListeners)(store.dispatch, props.setupListeners), [props.setupListeners, store.dispatch]);
return /* @__PURE__ */ React.createElement(import_react_redux.Provider, { store, context }, props.children);
}
// src/query/react/index.ts
var createApi = /* @__PURE__ */ (0, import_query.buildCreateApi)((0, import_query.coreModule)(), reactHooksModule());
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ApiProvider,
UNINITIALIZED_VALUE,
createApi,
reactHooksModule,
reactHooksModuleName,
...require("@reduxjs/toolkit/query")
});
//# sourceMappingURL=rtk-query-react.development.cjs.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,740 @@
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
// src/query/react/rtkqImports.ts
import { buildCreateApi, coreModule, copyWithStructuralSharing, setupListeners, QueryStatus, skipToken } from "@reduxjs/toolkit/query";
// src/query/react/module.ts
import { formatProdErrorMessage as _formatProdErrorMessage4 } from "@reduxjs/toolkit";
import { batch as rrBatch, useDispatch as rrUseDispatch, useSelector as rrUseSelector, useStore as rrUseStore } from "react-redux";
import { createSelector as _createSelector } from "reselect";
// src/query/utils/capitalize.ts
function capitalize(str) {
return str.replace(str[0], str[0].toUpperCase());
}
// src/query/utils/countObjectKeys.ts
function countObjectKeys(obj) {
let count = 0;
for (const _key in obj) {
count++;
}
return count;
}
// src/query/endpointDefinitions.ts
var ENDPOINT_QUERY = "query" /* query */;
var ENDPOINT_MUTATION = "mutation" /* mutation */;
var ENDPOINT_INFINITEQUERY = "infinitequery" /* infinitequery */;
function isQueryDefinition(e) {
return e.type === ENDPOINT_QUERY;
}
function isMutationDefinition(e) {
return e.type === ENDPOINT_MUTATION;
}
function isInfiniteQueryDefinition(e) {
return e.type === ENDPOINT_INFINITEQUERY;
}
// src/query/tsHelpers.ts
function safeAssign(target, ...args) {
return Object.assign(target, ...args);
}
// src/query/react/buildHooks.ts
import { formatProdErrorMessage as _formatProdErrorMessage, formatProdErrorMessage as _formatProdErrorMessage2, formatProdErrorMessage as _formatProdErrorMessage3 } from "@reduxjs/toolkit";
// src/query/react/constants.ts
var UNINITIALIZED_VALUE = /* @__PURE__ */ Symbol();
// src/query/react/reactImports.ts
import { useEffect, useRef, useMemo, useContext, useCallback, useDebugValue, useLayoutEffect, useState } from "react";
// src/query/react/reactReduxImports.ts
import { shallowEqual, Provider, ReactReduxContext } from "react-redux";
// src/query/react/useSerializedStableValue.ts
function useStableQueryArgs(queryArgs) {
const cache = useRef(queryArgs);
const copy = useMemo(() => copyWithStructuralSharing(cache.current, queryArgs), [queryArgs]);
useEffect(() => {
if (cache.current !== copy) {
cache.current = copy;
}
}, [copy]);
return copy;
}
// src/query/react/useShallowStableValue.ts
function useShallowStableValue(value) {
const cache = useRef(value);
useEffect(() => {
if (!shallowEqual(cache.current, value)) {
cache.current = value;
}
}, [value]);
return shallowEqual(cache.current, value) ? cache.current : value;
}
// src/query/react/buildHooks.ts
var canUseDOM = () => !!(typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined");
var isDOM = /* @__PURE__ */ canUseDOM();
var isRunningInReactNative = () => typeof navigator !== "undefined" && navigator.product === "ReactNative";
var isReactNative = /* @__PURE__ */ isRunningInReactNative();
var getUseIsomorphicLayoutEffect = () => isDOM || isReactNative ? useLayoutEffect : useEffect;
var useIsomorphicLayoutEffect = /* @__PURE__ */ getUseIsomorphicLayoutEffect();
var noPendingQueryStateSelector = (selected) => {
if (selected.isUninitialized) {
return __spreadProps(__spreadValues({}, selected), {
isUninitialized: false,
isFetching: true,
isLoading: selected.data !== void 0 ? false : true,
// This is the one place where we still have to use `QueryStatus` as an enum,
// since it's the only reference in the React package and not in the core.
status: QueryStatus.pending
});
}
return selected;
};
function pick(obj, ...keys) {
const ret = {};
keys.forEach((key) => {
ret[key] = obj[key];
});
return ret;
}
var COMMON_HOOK_DEBUG_FIELDS = ["data", "status", "isLoading", "isSuccess", "isError", "error"];
function buildHooks({
api,
moduleOptions: {
batch,
hooks: {
useDispatch,
useSelector,
useStore
},
unstable__sideEffectsInRender,
createSelector
},
serializeQueryArgs,
context
}) {
const usePossiblyImmediateEffect = unstable__sideEffectsInRender ? (cb) => cb() : useEffect;
const unsubscribePromiseRef = (ref) => {
var _a, _b;
return (_b = (_a = ref.current) == null ? void 0 : _a.unsubscribe) == null ? void 0 : _b.call(_a);
};
const endpointDefinitions = context.endpointDefinitions;
return {
buildQueryHooks,
buildInfiniteQueryHooks,
buildMutationHook,
usePrefetch
};
function queryStatePreSelector(currentState, lastResult, queryArgs) {
if ((lastResult == null ? void 0 : lastResult.endpointName) && currentState.isUninitialized) {
const {
endpointName
} = lastResult;
const endpointDefinition = endpointDefinitions[endpointName];
if (queryArgs !== skipToken && serializeQueryArgs({
queryArgs: lastResult.originalArgs,
endpointDefinition,
endpointName
}) === serializeQueryArgs({
queryArgs,
endpointDefinition,
endpointName
})) lastResult = void 0;
}
let data = currentState.isSuccess ? currentState.data : lastResult == null ? void 0 : lastResult.data;
if (data === void 0) data = currentState.data;
const hasData = data !== void 0;
const isFetching = currentState.isLoading;
const isLoading = (!lastResult || lastResult.isLoading || lastResult.isUninitialized) && !hasData && isFetching;
const isSuccess = currentState.isSuccess || hasData && (isFetching && !(lastResult == null ? void 0 : lastResult.isError) || currentState.isUninitialized);
return __spreadProps(__spreadValues({}, currentState), {
data,
currentData: currentState.data,
isFetching,
isLoading,
isSuccess
});
}
function infiniteQueryStatePreSelector(currentState, lastResult, queryArgs) {
if ((lastResult == null ? void 0 : lastResult.endpointName) && currentState.isUninitialized) {
const {
endpointName
} = lastResult;
const endpointDefinition = endpointDefinitions[endpointName];
if (queryArgs !== skipToken && serializeQueryArgs({
queryArgs: lastResult.originalArgs,
endpointDefinition,
endpointName
}) === serializeQueryArgs({
queryArgs,
endpointDefinition,
endpointName
})) lastResult = void 0;
}
let data = currentState.isSuccess ? currentState.data : lastResult == null ? void 0 : lastResult.data;
if (data === void 0) data = currentState.data;
const hasData = data !== void 0;
const isFetching = currentState.isLoading;
const isLoading = (!lastResult || lastResult.isLoading || lastResult.isUninitialized) && !hasData && isFetching;
const isSuccess = currentState.isSuccess || hasData && (isFetching && !(lastResult == null ? void 0 : lastResult.isError) || currentState.isUninitialized);
return __spreadProps(__spreadValues({}, currentState), {
data,
currentData: currentState.data,
isFetching,
isLoading,
isSuccess
});
}
function usePrefetch(endpointName, defaultOptions) {
const dispatch = useDispatch();
const stableDefaultOptions = useShallowStableValue(defaultOptions);
return useCallback((arg, options) => dispatch(api.util.prefetch(endpointName, arg, __spreadValues(__spreadValues({}, stableDefaultOptions), options))), [endpointName, dispatch, stableDefaultOptions]);
}
function useQuerySubscriptionCommonImpl(endpointName, arg, _a = {}) {
var _b = _a, {
refetchOnReconnect,
refetchOnFocus,
refetchOnMountOrArgChange,
skip = false,
pollingInterval = 0,
skipPollingIfUnfocused = false
} = _b, rest = __objRest(_b, [
"refetchOnReconnect",
"refetchOnFocus",
"refetchOnMountOrArgChange",
"skip",
"pollingInterval",
"skipPollingIfUnfocused"
]);
const {
initiate
} = api.endpoints[endpointName];
const dispatch = useDispatch();
const subscriptionSelectorsRef = useRef(void 0);
if (!subscriptionSelectorsRef.current) {
const returnedValue = dispatch(api.internalActions.internal_getRTKQSubscriptions());
if (process.env.NODE_ENV !== "production") {
if (typeof returnedValue !== "object" || typeof (returnedValue == null ? void 0 : returnedValue.type) === "string") {
throw new Error(process.env.NODE_ENV === "production" ? _formatProdErrorMessage(37) : `Warning: Middleware for RTK-Query API at reducerPath "${api.reducerPath}" has not been added to the store.
You must add the middleware for RTK-Query to function correctly!`);
}
}
subscriptionSelectorsRef.current = returnedValue;
}
const stableArg = useStableQueryArgs(skip ? skipToken : arg);
const stableSubscriptionOptions = useShallowStableValue({
refetchOnReconnect,
refetchOnFocus,
pollingInterval,
skipPollingIfUnfocused
});
const initialPageParam = rest.initialPageParam;
const stableInitialPageParam = useShallowStableValue(initialPageParam);
const refetchCachedPages = rest.refetchCachedPages;
const stableRefetchCachedPages = useShallowStableValue(refetchCachedPages);
const promiseRef = useRef(void 0);
let {
queryCacheKey,
requestId
} = promiseRef.current || {};
let currentRenderHasSubscription = false;
if (queryCacheKey && requestId) {
currentRenderHasSubscription = subscriptionSelectorsRef.current.isRequestSubscribed(queryCacheKey, requestId);
}
const subscriptionRemoved = !currentRenderHasSubscription && promiseRef.current !== void 0;
usePossiblyImmediateEffect(() => {
if (subscriptionRemoved) {
promiseRef.current = void 0;
}
}, [subscriptionRemoved]);
usePossiblyImmediateEffect(() => {
var _a2;
const lastPromise = promiseRef.current;
if (typeof process !== "undefined" && process.env.NODE_ENV === "removeMeOnCompilation") {
console.log(subscriptionRemoved);
}
if (stableArg === skipToken) {
lastPromise == null ? void 0 : lastPromise.unsubscribe();
promiseRef.current = void 0;
return;
}
const lastSubscriptionOptions = (_a2 = promiseRef.current) == null ? void 0 : _a2.subscriptionOptions;
if (!lastPromise || lastPromise.arg !== stableArg) {
lastPromise == null ? void 0 : lastPromise.unsubscribe();
const promise = dispatch(initiate(stableArg, __spreadValues({
subscriptionOptions: stableSubscriptionOptions,
forceRefetch: refetchOnMountOrArgChange
}, isInfiniteQueryDefinition(endpointDefinitions[endpointName]) ? {
initialPageParam: stableInitialPageParam,
refetchCachedPages: stableRefetchCachedPages
} : {})));
promiseRef.current = promise;
} else if (stableSubscriptionOptions !== lastSubscriptionOptions) {
lastPromise.updateSubscriptionOptions(stableSubscriptionOptions);
}
}, [dispatch, initiate, refetchOnMountOrArgChange, stableArg, stableSubscriptionOptions, subscriptionRemoved, stableInitialPageParam, stableRefetchCachedPages, endpointName]);
return [promiseRef, dispatch, initiate, stableSubscriptionOptions];
}
function buildUseQueryState(endpointName, preSelector) {
const useQueryState = (arg, {
skip = false,
selectFromResult
} = {}) => {
const {
select
} = api.endpoints[endpointName];
const stableArg = useStableQueryArgs(skip ? skipToken : arg);
const lastValue = useRef(void 0);
const selectDefaultResult = useMemo(() => (
// Normally ts-ignores are bad and should be avoided, but we're
// already casting this selector to be `Selector<any>` anyway,
// so the inconsistencies don't matter here
// @ts-ignore
createSelector([
// @ts-ignore
select(stableArg),
(_, lastResult) => lastResult,
(_) => stableArg
], preSelector, {
memoizeOptions: {
resultEqualityCheck: shallowEqual
}
})
), [select, stableArg]);
const querySelector = useMemo(() => selectFromResult ? createSelector([selectDefaultResult], selectFromResult, {
devModeChecks: {
identityFunctionCheck: "never"
}
}) : selectDefaultResult, [selectDefaultResult, selectFromResult]);
const currentState = useSelector((state) => querySelector(state, lastValue.current), shallowEqual);
const store = useStore();
const newLastValue = selectDefaultResult(store.getState(), lastValue.current);
useIsomorphicLayoutEffect(() => {
lastValue.current = newLastValue;
}, [newLastValue]);
return currentState;
};
return useQueryState;
}
function usePromiseRefUnsubscribeOnUnmount(promiseRef) {
useEffect(() => {
return () => {
unsubscribePromiseRef(promiseRef);
promiseRef.current = void 0;
};
}, [promiseRef]);
}
function refetchOrErrorIfUnmounted(promiseRef) {
if (!promiseRef.current) throw new Error(process.env.NODE_ENV === "production" ? _formatProdErrorMessage2(38) : "Cannot refetch a query that has not been started yet.");
return promiseRef.current.refetch();
}
function buildQueryHooks(endpointName) {
const useQuerySubscription = (arg, options = {}) => {
const [promiseRef] = useQuerySubscriptionCommonImpl(endpointName, arg, options);
usePromiseRefUnsubscribeOnUnmount(promiseRef);
return useMemo(() => ({
/**
* A method to manually refetch data for the query
*/
refetch: () => refetchOrErrorIfUnmounted(promiseRef)
}), [promiseRef]);
};
const useLazyQuerySubscription = ({
refetchOnReconnect,
refetchOnFocus,
pollingInterval = 0,
skipPollingIfUnfocused = false
} = {}) => {
const {
initiate
} = api.endpoints[endpointName];
const dispatch = useDispatch();
const [arg, setArg] = useState(UNINITIALIZED_VALUE);
const promiseRef = useRef(void 0);
const stableSubscriptionOptions = useShallowStableValue({
refetchOnReconnect,
refetchOnFocus,
pollingInterval,
skipPollingIfUnfocused
});
usePossiblyImmediateEffect(() => {
var _a, _b;
const lastSubscriptionOptions = (_a = promiseRef.current) == null ? void 0 : _a.subscriptionOptions;
if (stableSubscriptionOptions !== lastSubscriptionOptions) {
(_b = promiseRef.current) == null ? void 0 : _b.updateSubscriptionOptions(stableSubscriptionOptions);
}
}, [stableSubscriptionOptions]);
const subscriptionOptionsRef = useRef(stableSubscriptionOptions);
usePossiblyImmediateEffect(() => {
subscriptionOptionsRef.current = stableSubscriptionOptions;
}, [stableSubscriptionOptions]);
const trigger = useCallback(function(arg2, preferCacheValue = false) {
let promise;
batch(() => {
unsubscribePromiseRef(promiseRef);
promiseRef.current = promise = dispatch(initiate(arg2, {
subscriptionOptions: subscriptionOptionsRef.current,
forceRefetch: !preferCacheValue
}));
setArg(arg2);
});
return promise;
}, [dispatch, initiate]);
const reset = useCallback(() => {
var _a, _b;
if ((_a = promiseRef.current) == null ? void 0 : _a.queryCacheKey) {
dispatch(api.internalActions.removeQueryResult({
queryCacheKey: (_b = promiseRef.current) == null ? void 0 : _b.queryCacheKey
}));
}
}, [dispatch]);
useEffect(() => {
return () => {
unsubscribePromiseRef(promiseRef);
};
}, []);
useEffect(() => {
if (arg !== UNINITIALIZED_VALUE && !promiseRef.current) {
trigger(arg, true);
}
}, [arg, trigger]);
return useMemo(() => [trigger, arg, {
reset
}], [trigger, arg, reset]);
};
const useQueryState = buildUseQueryState(endpointName, queryStatePreSelector);
return {
useQueryState,
useQuerySubscription,
useLazyQuerySubscription,
useLazyQuery(options) {
const [trigger, arg, {
reset
}] = useLazyQuerySubscription(options);
const queryStateResults = useQueryState(arg, __spreadProps(__spreadValues({}, options), {
skip: arg === UNINITIALIZED_VALUE
}));
const info = useMemo(() => ({
lastArg: arg
}), [arg]);
return useMemo(() => [trigger, __spreadProps(__spreadValues({}, queryStateResults), {
reset
}), info], [trigger, queryStateResults, reset, info]);
},
useQuery(arg, options) {
const querySubscriptionResults = useQuerySubscription(arg, options);
const queryStateResults = useQueryState(arg, __spreadValues({
selectFromResult: arg === skipToken || (options == null ? void 0 : options.skip) ? void 0 : noPendingQueryStateSelector
}, options));
const debugValue = pick(queryStateResults, ...COMMON_HOOK_DEBUG_FIELDS);
useDebugValue(debugValue);
return useMemo(() => __spreadValues(__spreadValues({}, queryStateResults), querySubscriptionResults), [queryStateResults, querySubscriptionResults]);
}
};
}
function buildInfiniteQueryHooks(endpointName) {
const useInfiniteQuerySubscription = (arg, options = {}) => {
const [promiseRef, dispatch, initiate, stableSubscriptionOptions] = useQuerySubscriptionCommonImpl(endpointName, arg, options);
const subscriptionOptionsRef = useRef(stableSubscriptionOptions);
usePossiblyImmediateEffect(() => {
subscriptionOptionsRef.current = stableSubscriptionOptions;
}, [stableSubscriptionOptions]);
const hookRefetchCachedPages = options.refetchCachedPages;
const stableHookRefetchCachedPages = useShallowStableValue(hookRefetchCachedPages);
const trigger = useCallback(function(arg2, direction) {
let promise;
batch(() => {
unsubscribePromiseRef(promiseRef);
promiseRef.current = promise = dispatch(initiate(arg2, {
subscriptionOptions: subscriptionOptionsRef.current,
direction
}));
});
return promise;
}, [promiseRef, dispatch, initiate]);
usePromiseRefUnsubscribeOnUnmount(promiseRef);
const stableArg = useStableQueryArgs(options.skip ? skipToken : arg);
const refetch = useCallback((options2) => {
var _a;
if (!promiseRef.current) throw new Error(process.env.NODE_ENV === "production" ? _formatProdErrorMessage3(38) : "Cannot refetch a query that has not been started yet.");
const mergedOptions = {
refetchCachedPages: (_a = options2 == null ? void 0 : options2.refetchCachedPages) != null ? _a : stableHookRefetchCachedPages
};
return promiseRef.current.refetch(mergedOptions);
}, [promiseRef, stableHookRefetchCachedPages]);
return useMemo(() => {
const fetchNextPage = () => {
return trigger(stableArg, "forward");
};
const fetchPreviousPage = () => {
return trigger(stableArg, "backward");
};
return {
trigger,
/**
* A method to manually refetch data for the query
*/
refetch,
fetchNextPage,
fetchPreviousPage
};
}, [refetch, trigger, stableArg]);
};
const useInfiniteQueryState = buildUseQueryState(endpointName, infiniteQueryStatePreSelector);
return {
useInfiniteQueryState,
useInfiniteQuerySubscription,
useInfiniteQuery(arg, options) {
const {
refetch,
fetchNextPage,
fetchPreviousPage
} = useInfiniteQuerySubscription(arg, options);
const queryStateResults = useInfiniteQueryState(arg, __spreadValues({
selectFromResult: arg === skipToken || (options == null ? void 0 : options.skip) ? void 0 : noPendingQueryStateSelector
}, options));
const debugValue = pick(queryStateResults, ...COMMON_HOOK_DEBUG_FIELDS, "hasNextPage", "hasPreviousPage");
useDebugValue(debugValue);
return useMemo(() => __spreadProps(__spreadValues({}, queryStateResults), {
fetchNextPage,
fetchPreviousPage,
refetch
}), [queryStateResults, fetchNextPage, fetchPreviousPage, refetch]);
}
};
}
function buildMutationHook(name) {
return ({
selectFromResult,
fixedCacheKey
} = {}) => {
const {
select,
initiate
} = api.endpoints[name];
const dispatch = useDispatch();
const [promise, setPromise] = useState();
useEffect(() => () => {
if (!(promise == null ? void 0 : promise.arg.fixedCacheKey)) {
promise == null ? void 0 : promise.reset();
}
}, [promise]);
const triggerMutation = useCallback(function(arg) {
const promise2 = dispatch(initiate(arg, {
fixedCacheKey
}));
setPromise(promise2);
return promise2;
}, [dispatch, initiate, fixedCacheKey]);
const {
requestId
} = promise || {};
const selectDefaultResult = useMemo(() => select({
fixedCacheKey,
requestId: promise == null ? void 0 : promise.requestId
}), [fixedCacheKey, promise, select]);
const mutationSelector = useMemo(() => selectFromResult ? createSelector([selectDefaultResult], selectFromResult) : selectDefaultResult, [selectFromResult, selectDefaultResult]);
const currentState = useSelector(mutationSelector, shallowEqual);
const originalArgs = fixedCacheKey == null ? promise == null ? void 0 : promise.arg.originalArgs : void 0;
const reset = useCallback(() => {
batch(() => {
if (promise) {
setPromise(void 0);
}
if (fixedCacheKey) {
dispatch(api.internalActions.removeMutationResult({
requestId,
fixedCacheKey
}));
}
});
}, [dispatch, fixedCacheKey, promise, requestId]);
const debugValue = pick(currentState, ...COMMON_HOOK_DEBUG_FIELDS, "endpointName");
useDebugValue(debugValue);
const finalState = useMemo(() => __spreadProps(__spreadValues({}, currentState), {
originalArgs,
reset
}), [currentState, originalArgs, reset]);
return useMemo(() => [triggerMutation, finalState], [triggerMutation, finalState]);
};
}
}
// src/query/react/module.ts
var reactHooksModuleName = /* @__PURE__ */ Symbol();
var reactHooksModule = (_a = {}) => {
var _b = _a, {
batch = rrBatch,
hooks = {
useDispatch: rrUseDispatch,
useSelector: rrUseSelector,
useStore: rrUseStore
},
createSelector = _createSelector,
unstable__sideEffectsInRender = false
} = _b, rest = __objRest(_b, [
"batch",
"hooks",
"createSelector",
"unstable__sideEffectsInRender"
]);
if (process.env.NODE_ENV !== "production") {
const hookNames = ["useDispatch", "useSelector", "useStore"];
let warned = false;
for (const hookName of hookNames) {
if (countObjectKeys(rest) > 0) {
if (rest[hookName]) {
if (!warned) {
console.warn("As of RTK 2.0, the hooks now need to be specified as one object, provided under a `hooks` key:\n`reactHooksModule({ hooks: { useDispatch, useSelector, useStore } })`");
warned = true;
}
}
hooks[hookName] = rest[hookName];
}
if (typeof hooks[hookName] !== "function") {
throw new Error(process.env.NODE_ENV === "production" ? _formatProdErrorMessage4(36) : `When using custom hooks for context, all ${hookNames.length} hooks need to be provided: ${hookNames.join(", ")}.
Hook ${hookName} was either not provided or not a function.`);
}
}
}
return {
name: reactHooksModuleName,
init(api, {
serializeQueryArgs
}, context) {
const anyApi = api;
const {
buildQueryHooks,
buildInfiniteQueryHooks,
buildMutationHook,
usePrefetch
} = buildHooks({
api,
moduleOptions: {
batch,
hooks,
unstable__sideEffectsInRender,
createSelector
},
serializeQueryArgs,
context
});
safeAssign(anyApi, {
usePrefetch
});
safeAssign(context, {
batch
});
return {
injectEndpoint(endpointName, definition) {
if (isQueryDefinition(definition)) {
const {
useQuery,
useLazyQuery,
useLazyQuerySubscription,
useQueryState,
useQuerySubscription
} = buildQueryHooks(endpointName);
safeAssign(anyApi.endpoints[endpointName], {
useQuery,
useLazyQuery,
useLazyQuerySubscription,
useQueryState,
useQuerySubscription
});
api[`use${capitalize(endpointName)}Query`] = useQuery;
api[`useLazy${capitalize(endpointName)}Query`] = useLazyQuery;
}
if (isMutationDefinition(definition)) {
const useMutation = buildMutationHook(endpointName);
safeAssign(anyApi.endpoints[endpointName], {
useMutation
});
api[`use${capitalize(endpointName)}Mutation`] = useMutation;
} else if (isInfiniteQueryDefinition(definition)) {
const {
useInfiniteQuery,
useInfiniteQuerySubscription,
useInfiniteQueryState
} = buildInfiniteQueryHooks(endpointName);
safeAssign(anyApi.endpoints[endpointName], {
useInfiniteQuery,
useInfiniteQuerySubscription,
useInfiniteQueryState
});
api[`use${capitalize(endpointName)}InfiniteQuery`] = useInfiniteQuery;
}
}
};
}
};
};
// src/query/react/index.ts
export * from "@reduxjs/toolkit/query";
// src/query/react/ApiProvider.tsx
import { configureStore, formatProdErrorMessage as _formatProdErrorMessage5 } from "@reduxjs/toolkit";
import * as React from "react";
function ApiProvider(props) {
const context = props.context || ReactReduxContext;
const existingContext = useContext(context);
if (existingContext) {
throw new Error(process.env.NODE_ENV === "production" ? _formatProdErrorMessage5(35) : "Existing Redux context detected. If you already have a store set up, please use the traditional Redux setup.");
}
const [store] = React.useState(() => configureStore({
reducer: {
[props.api.reducerPath]: props.api.reducer
},
middleware: (gDM) => gDM().concat(props.api.middleware)
}));
useEffect(() => props.setupListeners === false ? void 0 : setupListeners(store.dispatch, props.setupListeners), [props.setupListeners, store.dispatch]);
return /* @__PURE__ */ React.createElement(Provider, { store, context }, props.children);
}
// src/query/react/index.ts
var createApi = /* @__PURE__ */ buildCreateApi(coreModule(), reactHooksModule());
export {
ApiProvider,
UNINITIALIZED_VALUE,
createApi,
reactHooksModule,
reactHooksModuleName
};
//# sourceMappingURL=rtk-query-react.legacy-esm.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,705 @@
// src/query/react/rtkqImports.ts
import { buildCreateApi, coreModule, copyWithStructuralSharing, setupListeners, QueryStatus, skipToken } from "@reduxjs/toolkit/query";
// src/query/react/module.ts
import { formatProdErrorMessage as _formatProdErrorMessage4 } from "@reduxjs/toolkit";
import { batch as rrBatch, useDispatch as rrUseDispatch, useSelector as rrUseSelector, useStore as rrUseStore } from "react-redux";
import { createSelector as _createSelector } from "reselect";
// src/query/utils/capitalize.ts
function capitalize(str) {
return str.replace(str[0], str[0].toUpperCase());
}
// src/query/utils/countObjectKeys.ts
function countObjectKeys(obj) {
let count = 0;
for (const _key in obj) {
count++;
}
return count;
}
// src/query/endpointDefinitions.ts
var ENDPOINT_QUERY = "query" /* query */;
var ENDPOINT_MUTATION = "mutation" /* mutation */;
var ENDPOINT_INFINITEQUERY = "infinitequery" /* infinitequery */;
function isQueryDefinition(e) {
return e.type === ENDPOINT_QUERY;
}
function isMutationDefinition(e) {
return e.type === ENDPOINT_MUTATION;
}
function isInfiniteQueryDefinition(e) {
return e.type === ENDPOINT_INFINITEQUERY;
}
// src/query/tsHelpers.ts
function safeAssign(target, ...args) {
return Object.assign(target, ...args);
}
// src/query/react/buildHooks.ts
import { formatProdErrorMessage as _formatProdErrorMessage, formatProdErrorMessage as _formatProdErrorMessage2, formatProdErrorMessage as _formatProdErrorMessage3 } from "@reduxjs/toolkit";
// src/query/react/constants.ts
var UNINITIALIZED_VALUE = /* @__PURE__ */ Symbol();
// src/query/react/reactImports.ts
import { useEffect, useRef, useMemo, useContext, useCallback, useDebugValue, useLayoutEffect, useState } from "react";
// src/query/react/reactReduxImports.ts
import { shallowEqual, Provider, ReactReduxContext } from "react-redux";
// src/query/react/useSerializedStableValue.ts
function useStableQueryArgs(queryArgs) {
const cache = useRef(queryArgs);
const copy = useMemo(() => copyWithStructuralSharing(cache.current, queryArgs), [queryArgs]);
useEffect(() => {
if (cache.current !== copy) {
cache.current = copy;
}
}, [copy]);
return copy;
}
// src/query/react/useShallowStableValue.ts
function useShallowStableValue(value) {
const cache = useRef(value);
useEffect(() => {
if (!shallowEqual(cache.current, value)) {
cache.current = value;
}
}, [value]);
return shallowEqual(cache.current, value) ? cache.current : value;
}
// src/query/react/buildHooks.ts
var canUseDOM = () => !!(typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined");
var isDOM = /* @__PURE__ */ canUseDOM();
var isRunningInReactNative = () => typeof navigator !== "undefined" && navigator.product === "ReactNative";
var isReactNative = /* @__PURE__ */ isRunningInReactNative();
var getUseIsomorphicLayoutEffect = () => isDOM || isReactNative ? useLayoutEffect : useEffect;
var useIsomorphicLayoutEffect = /* @__PURE__ */ getUseIsomorphicLayoutEffect();
var noPendingQueryStateSelector = (selected) => {
if (selected.isUninitialized) {
return {
...selected,
isUninitialized: false,
isFetching: true,
isLoading: selected.data !== void 0 ? false : true,
// This is the one place where we still have to use `QueryStatus` as an enum,
// since it's the only reference in the React package and not in the core.
status: QueryStatus.pending
};
}
return selected;
};
function pick(obj, ...keys) {
const ret = {};
keys.forEach((key) => {
ret[key] = obj[key];
});
return ret;
}
var COMMON_HOOK_DEBUG_FIELDS = ["data", "status", "isLoading", "isSuccess", "isError", "error"];
function buildHooks({
api,
moduleOptions: {
batch,
hooks: {
useDispatch,
useSelector,
useStore
},
unstable__sideEffectsInRender,
createSelector
},
serializeQueryArgs,
context
}) {
const usePossiblyImmediateEffect = unstable__sideEffectsInRender ? (cb) => cb() : useEffect;
const unsubscribePromiseRef = (ref) => ref.current?.unsubscribe?.();
const endpointDefinitions = context.endpointDefinitions;
return {
buildQueryHooks,
buildInfiniteQueryHooks,
buildMutationHook,
usePrefetch
};
function queryStatePreSelector(currentState, lastResult, queryArgs) {
if (lastResult?.endpointName && currentState.isUninitialized) {
const {
endpointName
} = lastResult;
const endpointDefinition = endpointDefinitions[endpointName];
if (queryArgs !== skipToken && serializeQueryArgs({
queryArgs: lastResult.originalArgs,
endpointDefinition,
endpointName
}) === serializeQueryArgs({
queryArgs,
endpointDefinition,
endpointName
})) lastResult = void 0;
}
let data = currentState.isSuccess ? currentState.data : lastResult?.data;
if (data === void 0) data = currentState.data;
const hasData = data !== void 0;
const isFetching = currentState.isLoading;
const isLoading = (!lastResult || lastResult.isLoading || lastResult.isUninitialized) && !hasData && isFetching;
const isSuccess = currentState.isSuccess || hasData && (isFetching && !lastResult?.isError || currentState.isUninitialized);
return {
...currentState,
data,
currentData: currentState.data,
isFetching,
isLoading,
isSuccess
};
}
function infiniteQueryStatePreSelector(currentState, lastResult, queryArgs) {
if (lastResult?.endpointName && currentState.isUninitialized) {
const {
endpointName
} = lastResult;
const endpointDefinition = endpointDefinitions[endpointName];
if (queryArgs !== skipToken && serializeQueryArgs({
queryArgs: lastResult.originalArgs,
endpointDefinition,
endpointName
}) === serializeQueryArgs({
queryArgs,
endpointDefinition,
endpointName
})) lastResult = void 0;
}
let data = currentState.isSuccess ? currentState.data : lastResult?.data;
if (data === void 0) data = currentState.data;
const hasData = data !== void 0;
const isFetching = currentState.isLoading;
const isLoading = (!lastResult || lastResult.isLoading || lastResult.isUninitialized) && !hasData && isFetching;
const isSuccess = currentState.isSuccess || hasData && (isFetching && !lastResult?.isError || currentState.isUninitialized);
return {
...currentState,
data,
currentData: currentState.data,
isFetching,
isLoading,
isSuccess
};
}
function usePrefetch(endpointName, defaultOptions) {
const dispatch = useDispatch();
const stableDefaultOptions = useShallowStableValue(defaultOptions);
return useCallback((arg, options) => dispatch(api.util.prefetch(endpointName, arg, {
...stableDefaultOptions,
...options
})), [endpointName, dispatch, stableDefaultOptions]);
}
function useQuerySubscriptionCommonImpl(endpointName, arg, {
refetchOnReconnect,
refetchOnFocus,
refetchOnMountOrArgChange,
skip = false,
pollingInterval = 0,
skipPollingIfUnfocused = false,
...rest
} = {}) {
const {
initiate
} = api.endpoints[endpointName];
const dispatch = useDispatch();
const subscriptionSelectorsRef = useRef(void 0);
if (!subscriptionSelectorsRef.current) {
const returnedValue = dispatch(api.internalActions.internal_getRTKQSubscriptions());
if (process.env.NODE_ENV !== "production") {
if (typeof returnedValue !== "object" || typeof returnedValue?.type === "string") {
throw new Error(process.env.NODE_ENV === "production" ? _formatProdErrorMessage(37) : `Warning: Middleware for RTK-Query API at reducerPath "${api.reducerPath}" has not been added to the store.
You must add the middleware for RTK-Query to function correctly!`);
}
}
subscriptionSelectorsRef.current = returnedValue;
}
const stableArg = useStableQueryArgs(skip ? skipToken : arg);
const stableSubscriptionOptions = useShallowStableValue({
refetchOnReconnect,
refetchOnFocus,
pollingInterval,
skipPollingIfUnfocused
});
const initialPageParam = rest.initialPageParam;
const stableInitialPageParam = useShallowStableValue(initialPageParam);
const refetchCachedPages = rest.refetchCachedPages;
const stableRefetchCachedPages = useShallowStableValue(refetchCachedPages);
const promiseRef = useRef(void 0);
let {
queryCacheKey,
requestId
} = promiseRef.current || {};
let currentRenderHasSubscription = false;
if (queryCacheKey && requestId) {
currentRenderHasSubscription = subscriptionSelectorsRef.current.isRequestSubscribed(queryCacheKey, requestId);
}
const subscriptionRemoved = !currentRenderHasSubscription && promiseRef.current !== void 0;
usePossiblyImmediateEffect(() => {
if (subscriptionRemoved) {
promiseRef.current = void 0;
}
}, [subscriptionRemoved]);
usePossiblyImmediateEffect(() => {
const lastPromise = promiseRef.current;
if (typeof process !== "undefined" && process.env.NODE_ENV === "removeMeOnCompilation") {
console.log(subscriptionRemoved);
}
if (stableArg === skipToken) {
lastPromise?.unsubscribe();
promiseRef.current = void 0;
return;
}
const lastSubscriptionOptions = promiseRef.current?.subscriptionOptions;
if (!lastPromise || lastPromise.arg !== stableArg) {
lastPromise?.unsubscribe();
const promise = dispatch(initiate(stableArg, {
subscriptionOptions: stableSubscriptionOptions,
forceRefetch: refetchOnMountOrArgChange,
...isInfiniteQueryDefinition(endpointDefinitions[endpointName]) ? {
initialPageParam: stableInitialPageParam,
refetchCachedPages: stableRefetchCachedPages
} : {}
}));
promiseRef.current = promise;
} else if (stableSubscriptionOptions !== lastSubscriptionOptions) {
lastPromise.updateSubscriptionOptions(stableSubscriptionOptions);
}
}, [dispatch, initiate, refetchOnMountOrArgChange, stableArg, stableSubscriptionOptions, subscriptionRemoved, stableInitialPageParam, stableRefetchCachedPages, endpointName]);
return [promiseRef, dispatch, initiate, stableSubscriptionOptions];
}
function buildUseQueryState(endpointName, preSelector) {
const useQueryState = (arg, {
skip = false,
selectFromResult
} = {}) => {
const {
select
} = api.endpoints[endpointName];
const stableArg = useStableQueryArgs(skip ? skipToken : arg);
const lastValue = useRef(void 0);
const selectDefaultResult = useMemo(() => (
// Normally ts-ignores are bad and should be avoided, but we're
// already casting this selector to be `Selector<any>` anyway,
// so the inconsistencies don't matter here
// @ts-ignore
createSelector([
// @ts-ignore
select(stableArg),
(_, lastResult) => lastResult,
(_) => stableArg
], preSelector, {
memoizeOptions: {
resultEqualityCheck: shallowEqual
}
})
), [select, stableArg]);
const querySelector = useMemo(() => selectFromResult ? createSelector([selectDefaultResult], selectFromResult, {
devModeChecks: {
identityFunctionCheck: "never"
}
}) : selectDefaultResult, [selectDefaultResult, selectFromResult]);
const currentState = useSelector((state) => querySelector(state, lastValue.current), shallowEqual);
const store = useStore();
const newLastValue = selectDefaultResult(store.getState(), lastValue.current);
useIsomorphicLayoutEffect(() => {
lastValue.current = newLastValue;
}, [newLastValue]);
return currentState;
};
return useQueryState;
}
function usePromiseRefUnsubscribeOnUnmount(promiseRef) {
useEffect(() => {
return () => {
unsubscribePromiseRef(promiseRef);
promiseRef.current = void 0;
};
}, [promiseRef]);
}
function refetchOrErrorIfUnmounted(promiseRef) {
if (!promiseRef.current) throw new Error(process.env.NODE_ENV === "production" ? _formatProdErrorMessage2(38) : "Cannot refetch a query that has not been started yet.");
return promiseRef.current.refetch();
}
function buildQueryHooks(endpointName) {
const useQuerySubscription = (arg, options = {}) => {
const [promiseRef] = useQuerySubscriptionCommonImpl(endpointName, arg, options);
usePromiseRefUnsubscribeOnUnmount(promiseRef);
return useMemo(() => ({
/**
* A method to manually refetch data for the query
*/
refetch: () => refetchOrErrorIfUnmounted(promiseRef)
}), [promiseRef]);
};
const useLazyQuerySubscription = ({
refetchOnReconnect,
refetchOnFocus,
pollingInterval = 0,
skipPollingIfUnfocused = false
} = {}) => {
const {
initiate
} = api.endpoints[endpointName];
const dispatch = useDispatch();
const [arg, setArg] = useState(UNINITIALIZED_VALUE);
const promiseRef = useRef(void 0);
const stableSubscriptionOptions = useShallowStableValue({
refetchOnReconnect,
refetchOnFocus,
pollingInterval,
skipPollingIfUnfocused
});
usePossiblyImmediateEffect(() => {
const lastSubscriptionOptions = promiseRef.current?.subscriptionOptions;
if (stableSubscriptionOptions !== lastSubscriptionOptions) {
promiseRef.current?.updateSubscriptionOptions(stableSubscriptionOptions);
}
}, [stableSubscriptionOptions]);
const subscriptionOptionsRef = useRef(stableSubscriptionOptions);
usePossiblyImmediateEffect(() => {
subscriptionOptionsRef.current = stableSubscriptionOptions;
}, [stableSubscriptionOptions]);
const trigger = useCallback(function(arg2, preferCacheValue = false) {
let promise;
batch(() => {
unsubscribePromiseRef(promiseRef);
promiseRef.current = promise = dispatch(initiate(arg2, {
subscriptionOptions: subscriptionOptionsRef.current,
forceRefetch: !preferCacheValue
}));
setArg(arg2);
});
return promise;
}, [dispatch, initiate]);
const reset = useCallback(() => {
if (promiseRef.current?.queryCacheKey) {
dispatch(api.internalActions.removeQueryResult({
queryCacheKey: promiseRef.current?.queryCacheKey
}));
}
}, [dispatch]);
useEffect(() => {
return () => {
unsubscribePromiseRef(promiseRef);
};
}, []);
useEffect(() => {
if (arg !== UNINITIALIZED_VALUE && !promiseRef.current) {
trigger(arg, true);
}
}, [arg, trigger]);
return useMemo(() => [trigger, arg, {
reset
}], [trigger, arg, reset]);
};
const useQueryState = buildUseQueryState(endpointName, queryStatePreSelector);
return {
useQueryState,
useQuerySubscription,
useLazyQuerySubscription,
useLazyQuery(options) {
const [trigger, arg, {
reset
}] = useLazyQuerySubscription(options);
const queryStateResults = useQueryState(arg, {
...options,
skip: arg === UNINITIALIZED_VALUE
});
const info = useMemo(() => ({
lastArg: arg
}), [arg]);
return useMemo(() => [trigger, {
...queryStateResults,
reset
}, info], [trigger, queryStateResults, reset, info]);
},
useQuery(arg, options) {
const querySubscriptionResults = useQuerySubscription(arg, options);
const queryStateResults = useQueryState(arg, {
selectFromResult: arg === skipToken || options?.skip ? void 0 : noPendingQueryStateSelector,
...options
});
const debugValue = pick(queryStateResults, ...COMMON_HOOK_DEBUG_FIELDS);
useDebugValue(debugValue);
return useMemo(() => ({
...queryStateResults,
...querySubscriptionResults
}), [queryStateResults, querySubscriptionResults]);
}
};
}
function buildInfiniteQueryHooks(endpointName) {
const useInfiniteQuerySubscription = (arg, options = {}) => {
const [promiseRef, dispatch, initiate, stableSubscriptionOptions] = useQuerySubscriptionCommonImpl(endpointName, arg, options);
const subscriptionOptionsRef = useRef(stableSubscriptionOptions);
usePossiblyImmediateEffect(() => {
subscriptionOptionsRef.current = stableSubscriptionOptions;
}, [stableSubscriptionOptions]);
const hookRefetchCachedPages = options.refetchCachedPages;
const stableHookRefetchCachedPages = useShallowStableValue(hookRefetchCachedPages);
const trigger = useCallback(function(arg2, direction) {
let promise;
batch(() => {
unsubscribePromiseRef(promiseRef);
promiseRef.current = promise = dispatch(initiate(arg2, {
subscriptionOptions: subscriptionOptionsRef.current,
direction
}));
});
return promise;
}, [promiseRef, dispatch, initiate]);
usePromiseRefUnsubscribeOnUnmount(promiseRef);
const stableArg = useStableQueryArgs(options.skip ? skipToken : arg);
const refetch = useCallback((options2) => {
if (!promiseRef.current) throw new Error(process.env.NODE_ENV === "production" ? _formatProdErrorMessage3(38) : "Cannot refetch a query that has not been started yet.");
const mergedOptions = {
refetchCachedPages: options2?.refetchCachedPages ?? stableHookRefetchCachedPages
};
return promiseRef.current.refetch(mergedOptions);
}, [promiseRef, stableHookRefetchCachedPages]);
return useMemo(() => {
const fetchNextPage = () => {
return trigger(stableArg, "forward");
};
const fetchPreviousPage = () => {
return trigger(stableArg, "backward");
};
return {
trigger,
/**
* A method to manually refetch data for the query
*/
refetch,
fetchNextPage,
fetchPreviousPage
};
}, [refetch, trigger, stableArg]);
};
const useInfiniteQueryState = buildUseQueryState(endpointName, infiniteQueryStatePreSelector);
return {
useInfiniteQueryState,
useInfiniteQuerySubscription,
useInfiniteQuery(arg, options) {
const {
refetch,
fetchNextPage,
fetchPreviousPage
} = useInfiniteQuerySubscription(arg, options);
const queryStateResults = useInfiniteQueryState(arg, {
selectFromResult: arg === skipToken || options?.skip ? void 0 : noPendingQueryStateSelector,
...options
});
const debugValue = pick(queryStateResults, ...COMMON_HOOK_DEBUG_FIELDS, "hasNextPage", "hasPreviousPage");
useDebugValue(debugValue);
return useMemo(() => ({
...queryStateResults,
fetchNextPage,
fetchPreviousPage,
refetch
}), [queryStateResults, fetchNextPage, fetchPreviousPage, refetch]);
}
};
}
function buildMutationHook(name) {
return ({
selectFromResult,
fixedCacheKey
} = {}) => {
const {
select,
initiate
} = api.endpoints[name];
const dispatch = useDispatch();
const [promise, setPromise] = useState();
useEffect(() => () => {
if (!promise?.arg.fixedCacheKey) {
promise?.reset();
}
}, [promise]);
const triggerMutation = useCallback(function(arg) {
const promise2 = dispatch(initiate(arg, {
fixedCacheKey
}));
setPromise(promise2);
return promise2;
}, [dispatch, initiate, fixedCacheKey]);
const {
requestId
} = promise || {};
const selectDefaultResult = useMemo(() => select({
fixedCacheKey,
requestId: promise?.requestId
}), [fixedCacheKey, promise, select]);
const mutationSelector = useMemo(() => selectFromResult ? createSelector([selectDefaultResult], selectFromResult) : selectDefaultResult, [selectFromResult, selectDefaultResult]);
const currentState = useSelector(mutationSelector, shallowEqual);
const originalArgs = fixedCacheKey == null ? promise?.arg.originalArgs : void 0;
const reset = useCallback(() => {
batch(() => {
if (promise) {
setPromise(void 0);
}
if (fixedCacheKey) {
dispatch(api.internalActions.removeMutationResult({
requestId,
fixedCacheKey
}));
}
});
}, [dispatch, fixedCacheKey, promise, requestId]);
const debugValue = pick(currentState, ...COMMON_HOOK_DEBUG_FIELDS, "endpointName");
useDebugValue(debugValue);
const finalState = useMemo(() => ({
...currentState,
originalArgs,
reset
}), [currentState, originalArgs, reset]);
return useMemo(() => [triggerMutation, finalState], [triggerMutation, finalState]);
};
}
}
// src/query/react/module.ts
var reactHooksModuleName = /* @__PURE__ */ Symbol();
var reactHooksModule = ({
batch = rrBatch,
hooks = {
useDispatch: rrUseDispatch,
useSelector: rrUseSelector,
useStore: rrUseStore
},
createSelector = _createSelector,
unstable__sideEffectsInRender = false,
...rest
} = {}) => {
if (process.env.NODE_ENV !== "production") {
const hookNames = ["useDispatch", "useSelector", "useStore"];
let warned = false;
for (const hookName of hookNames) {
if (countObjectKeys(rest) > 0) {
if (rest[hookName]) {
if (!warned) {
console.warn("As of RTK 2.0, the hooks now need to be specified as one object, provided under a `hooks` key:\n`reactHooksModule({ hooks: { useDispatch, useSelector, useStore } })`");
warned = true;
}
}
hooks[hookName] = rest[hookName];
}
if (typeof hooks[hookName] !== "function") {
throw new Error(process.env.NODE_ENV === "production" ? _formatProdErrorMessage4(36) : `When using custom hooks for context, all ${hookNames.length} hooks need to be provided: ${hookNames.join(", ")}.
Hook ${hookName} was either not provided or not a function.`);
}
}
}
return {
name: reactHooksModuleName,
init(api, {
serializeQueryArgs
}, context) {
const anyApi = api;
const {
buildQueryHooks,
buildInfiniteQueryHooks,
buildMutationHook,
usePrefetch
} = buildHooks({
api,
moduleOptions: {
batch,
hooks,
unstable__sideEffectsInRender,
createSelector
},
serializeQueryArgs,
context
});
safeAssign(anyApi, {
usePrefetch
});
safeAssign(context, {
batch
});
return {
injectEndpoint(endpointName, definition) {
if (isQueryDefinition(definition)) {
const {
useQuery,
useLazyQuery,
useLazyQuerySubscription,
useQueryState,
useQuerySubscription
} = buildQueryHooks(endpointName);
safeAssign(anyApi.endpoints[endpointName], {
useQuery,
useLazyQuery,
useLazyQuerySubscription,
useQueryState,
useQuerySubscription
});
api[`use${capitalize(endpointName)}Query`] = useQuery;
api[`useLazy${capitalize(endpointName)}Query`] = useLazyQuery;
}
if (isMutationDefinition(definition)) {
const useMutation = buildMutationHook(endpointName);
safeAssign(anyApi.endpoints[endpointName], {
useMutation
});
api[`use${capitalize(endpointName)}Mutation`] = useMutation;
} else if (isInfiniteQueryDefinition(definition)) {
const {
useInfiniteQuery,
useInfiniteQuerySubscription,
useInfiniteQueryState
} = buildInfiniteQueryHooks(endpointName);
safeAssign(anyApi.endpoints[endpointName], {
useInfiniteQuery,
useInfiniteQuerySubscription,
useInfiniteQueryState
});
api[`use${capitalize(endpointName)}InfiniteQuery`] = useInfiniteQuery;
}
}
};
}
};
};
// src/query/react/index.ts
export * from "@reduxjs/toolkit/query";
// src/query/react/ApiProvider.tsx
import { configureStore, formatProdErrorMessage as _formatProdErrorMessage5 } from "@reduxjs/toolkit";
import * as React from "react";
function ApiProvider(props) {
const context = props.context || ReactReduxContext;
const existingContext = useContext(context);
if (existingContext) {
throw new Error(process.env.NODE_ENV === "production" ? _formatProdErrorMessage5(35) : "Existing Redux context detected. If you already have a store set up, please use the traditional Redux setup.");
}
const [store] = React.useState(() => configureStore({
reducer: {
[props.api.reducerPath]: props.api.reducer
},
middleware: (gDM) => gDM().concat(props.api.middleware)
}));
useEffect(() => props.setupListeners === false ? void 0 : setupListeners(store.dispatch, props.setupListeners), [props.setupListeners, store.dispatch]);
return /* @__PURE__ */ React.createElement(Provider, { store, context }, props.children);
}
// src/query/react/index.ts
var createApi = /* @__PURE__ */ buildCreateApi(coreModule(), reactHooksModule());
export {
ApiProvider,
UNINITIALIZED_VALUE,
createApi,
reactHooksModule,
reactHooksModuleName
};
//# sourceMappingURL=rtk-query-react.modern.mjs.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,6 @@
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./redux-toolkit-react.production.min.cjs')
} else {
module.exports = require('./redux-toolkit-react.development.cjs')
}

View file

@ -0,0 +1,55 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/react/index.ts
var react_exports = {};
__export(react_exports, {
createDynamicMiddleware: () => createDynamicMiddleware
});
module.exports = __toCommonJS(react_exports);
__reExport(react_exports, require("@reduxjs/toolkit"), module.exports);
// src/dynamicMiddleware/react/index.ts
var import_toolkit = require("@reduxjs/toolkit");
var import_react_redux = require("react-redux");
var createDynamicMiddleware = () => {
const instance = (0, import_toolkit.createDynamicMiddleware)();
const createDispatchWithMiddlewareHookFactory = (context = import_react_redux.ReactReduxContext) => {
const useDispatch = context === import_react_redux.ReactReduxContext ? import_react_redux.useDispatch : (0, import_react_redux.createDispatchHook)(context);
function createDispatchWithMiddlewareHook2(...middlewares) {
instance.addMiddleware(...middlewares);
return useDispatch;
}
createDispatchWithMiddlewareHook2.withTypes = () => createDispatchWithMiddlewareHook2;
return createDispatchWithMiddlewareHook2;
};
const createDispatchWithMiddlewareHook = createDispatchWithMiddlewareHookFactory();
return {
...instance,
createDispatchWithMiddlewareHookFactory,
createDispatchWithMiddlewareHook
};
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createDynamicMiddleware,
...require("@reduxjs/toolkit")
});
//# sourceMappingURL=redux-toolkit-react.development.cjs.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/react/index.ts","../../../src/dynamicMiddleware/react/index.ts"],"sourcesContent":["// This must remain here so that the `mangleErrors.cjs` build script\n// does not have to import this into each source file it rewrites.\nimport { formatProdErrorMessage } from '@reduxjs/toolkit';\nexport * from '@reduxjs/toolkit';\nexport { createDynamicMiddleware } from '../dynamicMiddleware/react';\nexport type { CreateDispatchWithMiddlewareHook } from '../dynamicMiddleware/react/index';","import type { DynamicMiddlewareInstance, GetDispatch, GetState, MiddlewareApiConfig, TSHelpersExtractDispatchExtensions } from '@reduxjs/toolkit';\nimport { createDynamicMiddleware as cDM } from '@reduxjs/toolkit';\nimport type { Context } from 'react';\nimport type { ReactReduxContextValue } from 'react-redux';\nimport { createDispatchHook, ReactReduxContext, useDispatch as useDefaultDispatch } from 'react-redux';\nimport type { Action, Dispatch, Middleware, UnknownAction } from 'redux';\nexport type UseDispatchWithMiddlewareHook<Middlewares extends Middleware<any, State, DispatchType>[] = [], State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = () => TSHelpersExtractDispatchExtensions<Middlewares> & DispatchType;\nexport type CreateDispatchWithMiddlewareHook<State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = {\n <Middlewares extends [Middleware<any, State, DispatchType>, ...Middleware<any, State, DispatchType>[]]>(...middlewares: Middlewares): UseDispatchWithMiddlewareHook<Middlewares, State, DispatchType>;\n withTypes<MiddlewareConfig extends MiddlewareApiConfig>(): CreateDispatchWithMiddlewareHook<GetState<MiddlewareConfig>, GetDispatch<MiddlewareConfig>>;\n};\ntype ActionFromDispatch<DispatchType extends Dispatch<Action>> = DispatchType extends Dispatch<infer Action> ? Action : never;\ntype ReactDynamicMiddlewareInstance<State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = DynamicMiddlewareInstance<State, DispatchType> & {\n createDispatchWithMiddlewareHookFactory: (context?: Context<ReactReduxContextValue<State, ActionFromDispatch<DispatchType>> | null>) => CreateDispatchWithMiddlewareHook<State, DispatchType>;\n createDispatchWithMiddlewareHook: CreateDispatchWithMiddlewareHook<State, DispatchType>;\n};\nexport const createDynamicMiddleware = <State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>>(): ReactDynamicMiddlewareInstance<State, DispatchType> => {\n const instance = cDM<State, DispatchType>();\n const createDispatchWithMiddlewareHookFactory = (\n // @ts-ignore\n context: Context<ReactReduxContextValue<State, ActionFromDispatch<DispatchType>> | null> = ReactReduxContext) => {\n const useDispatch = context === ReactReduxContext ? useDefaultDispatch : createDispatchHook(context);\n function createDispatchWithMiddlewareHook<Middlewares extends Middleware<any, State, DispatchType>[]>(...middlewares: Middlewares) {\n instance.addMiddleware(...middlewares);\n return useDispatch;\n }\n createDispatchWithMiddlewareHook.withTypes = () => createDispatchWithMiddlewareHook;\n return createDispatchWithMiddlewareHook as CreateDispatchWithMiddlewareHook<State, DispatchType>;\n };\n const createDispatchWithMiddlewareHook = createDispatchWithMiddlewareHookFactory();\n return {\n ...instance,\n createDispatchWithMiddlewareHookFactory,\n createDispatchWithMiddlewareHook\n };\n};"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,0BAAc,6BAHd;;;ACCA,qBAA+C;AAG/C,yBAAyF;AAYlF,IAAM,0BAA0B,MAAgJ;AACrL,QAAM,eAAW,eAAAA,yBAAyB;AAC1C,QAAM,0CAA0C,CAEhD,UAA2F,yCAAsB;AAC/G,UAAM,cAAc,YAAY,uCAAoB,mBAAAC,kBAAqB,uCAAmB,OAAO;AACnG,aAASC,qCAAgG,aAA0B;AACjI,eAAS,cAAc,GAAG,WAAW;AACrC,aAAO;AAAA,IACT;AACA,IAAAA,kCAAiC,YAAY,MAAMA;AACnD,WAAOA;AAAA,EACT;AACA,QAAM,mCAAmC,wCAAwC;AACjF,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA;AAAA,EACF;AACF;","names":["cDM","useDefaultDispatch","createDispatchWithMiddlewareHook"]}

View file

@ -0,0 +1,2 @@
"use strict";var s=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var M=Object.prototype.hasOwnProperty;var x=(t,e)=>{for(var a in e)s(t,a,{get:e[a],enumerable:!0})},d=(t,e,a,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of y(e))!M.call(t,i)&&i!==a&&s(t,i,{get:()=>e[i],enumerable:!(n=w(e,i))||n.enumerable});return t},r=(t,e,a)=>(d(t,e,"default"),a&&d(a,e,"default"));var m=t=>d(s({},"__esModule",{value:!0}),t);var o={};x(o,{createDynamicMiddleware:()=>D});module.exports=m(o);r(o,require("@reduxjs/toolkit"),module.exports);var h=require("@reduxjs/toolkit"),c=require("react-redux"),D=()=>{let t=(0,h.createDynamicMiddleware)(),e=(n=c.ReactReduxContext)=>{let i=n===c.ReactReduxContext?c.useDispatch:(0,c.createDispatchHook)(n);function p(...l){return t.addMiddleware(...l),i}return p.withTypes=()=>p,p},a=e();return{...t,createDispatchWithMiddlewareHookFactory:e,createDispatchWithMiddlewareHook:a}};0&&(module.exports={createDynamicMiddleware,...require("@reduxjs/toolkit")});
//# sourceMappingURL=redux-toolkit-react.production.min.cjs.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/react/index.ts","../../../src/dynamicMiddleware/react/index.ts"],"sourcesContent":["// This must remain here so that the `mangleErrors.cjs` build script\n// does not have to import this into each source file it rewrites.\nimport { formatProdErrorMessage } from '@reduxjs/toolkit';\nexport * from '@reduxjs/toolkit';\nexport { createDynamicMiddleware } from '../dynamicMiddleware/react';\nexport type { CreateDispatchWithMiddlewareHook } from '../dynamicMiddleware/react/index';","import type { DynamicMiddlewareInstance, GetDispatch, GetState, MiddlewareApiConfig, TSHelpersExtractDispatchExtensions } from '@reduxjs/toolkit';\nimport { createDynamicMiddleware as cDM } from '@reduxjs/toolkit';\nimport type { Context } from 'react';\nimport type { ReactReduxContextValue } from 'react-redux';\nimport { createDispatchHook, ReactReduxContext, useDispatch as useDefaultDispatch } from 'react-redux';\nimport type { Action, Dispatch, Middleware, UnknownAction } from 'redux';\nexport type UseDispatchWithMiddlewareHook<Middlewares extends Middleware<any, State, DispatchType>[] = [], State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = () => TSHelpersExtractDispatchExtensions<Middlewares> & DispatchType;\nexport type CreateDispatchWithMiddlewareHook<State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = {\n <Middlewares extends [Middleware<any, State, DispatchType>, ...Middleware<any, State, DispatchType>[]]>(...middlewares: Middlewares): UseDispatchWithMiddlewareHook<Middlewares, State, DispatchType>;\n withTypes<MiddlewareConfig extends MiddlewareApiConfig>(): CreateDispatchWithMiddlewareHook<GetState<MiddlewareConfig>, GetDispatch<MiddlewareConfig>>;\n};\ntype ActionFromDispatch<DispatchType extends Dispatch<Action>> = DispatchType extends Dispatch<infer Action> ? Action : never;\ntype ReactDynamicMiddlewareInstance<State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = DynamicMiddlewareInstance<State, DispatchType> & {\n createDispatchWithMiddlewareHookFactory: (context?: Context<ReactReduxContextValue<State, ActionFromDispatch<DispatchType>> | null>) => CreateDispatchWithMiddlewareHook<State, DispatchType>;\n createDispatchWithMiddlewareHook: CreateDispatchWithMiddlewareHook<State, DispatchType>;\n};\nexport const createDynamicMiddleware = <State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>>(): ReactDynamicMiddlewareInstance<State, DispatchType> => {\n const instance = cDM<State, DispatchType>();\n const createDispatchWithMiddlewareHookFactory = (\n // @ts-ignore\n context: Context<ReactReduxContextValue<State, ActionFromDispatch<DispatchType>> | null> = ReactReduxContext) => {\n const useDispatch = context === ReactReduxContext ? useDefaultDispatch : createDispatchHook(context);\n function createDispatchWithMiddlewareHook<Middlewares extends Middleware<any, State, DispatchType>[]>(...middlewares: Middlewares) {\n instance.addMiddleware(...middlewares);\n return useDispatch;\n }\n createDispatchWithMiddlewareHook.withTypes = () => createDispatchWithMiddlewareHook;\n return createDispatchWithMiddlewareHook as CreateDispatchWithMiddlewareHook<State, DispatchType>;\n };\n const createDispatchWithMiddlewareHook = createDispatchWithMiddlewareHookFactory();\n return {\n ...instance,\n createDispatchWithMiddlewareHookFactory,\n createDispatchWithMiddlewareHook\n };\n};"],"mappings":"2dAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,6BAAAE,IAAA,eAAAC,EAAAH,GAGAI,EAAAJ,EAAc,4BAHd,gBCCA,IAAAK,EAA+C,4BAG/CC,EAAyF,uBAY5EC,EAA0B,IAAgJ,CACrL,IAAMC,KAAW,EAAAC,yBAAyB,EACpCC,EAA0C,CAEhDC,EAA2F,sBAAsB,CAC/G,IAAMC,EAAcD,IAAY,oBAAoB,EAAAE,eAAqB,sBAAmBF,CAAO,EACnG,SAASG,KAAgGC,EAA0B,CACjI,OAAAP,EAAS,cAAc,GAAGO,CAAW,EAC9BH,CACT,CACA,OAAAE,EAAiC,UAAY,IAAMA,EAC5CA,CACT,EACMA,EAAmCJ,EAAwC,EACjF,MAAO,CACL,GAAGF,EACH,wCAAAE,EACA,iCAAAI,CACF,CACF","names":["react_exports","__export","createDynamicMiddleware","__toCommonJS","__reExport","import_toolkit","import_react_redux","createDynamicMiddleware","instance","cDM","createDispatchWithMiddlewareHookFactory","context","useDispatch","useDefaultDispatch","createDispatchWithMiddlewareHook","middlewares"]}

View file

@ -0,0 +1,22 @@
import { TSHelpersExtractDispatchExtensions, MiddlewareApiConfig, GetState, GetDispatch, DynamicMiddlewareInstance } from '@reduxjs/toolkit';
export * from '@reduxjs/toolkit';
import { Context } from 'react';
import { ReactReduxContextValue } from 'react-redux';
import { Dispatch, UnknownAction, Middleware, Action } from 'redux';
type UseDispatchWithMiddlewareHook<Middlewares extends Middleware<any, State, DispatchType>[] = [], State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = () => TSHelpersExtractDispatchExtensions<Middlewares> & DispatchType;
type CreateDispatchWithMiddlewareHook<State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = {
<Middlewares extends [
Middleware<any, State, DispatchType>,
...Middleware<any, State, DispatchType>[]
]>(...middlewares: Middlewares): UseDispatchWithMiddlewareHook<Middlewares, State, DispatchType>;
withTypes<MiddlewareConfig extends MiddlewareApiConfig>(): CreateDispatchWithMiddlewareHook<GetState<MiddlewareConfig>, GetDispatch<MiddlewareConfig>>;
};
type ActionFromDispatch<DispatchType extends Dispatch<Action>> = DispatchType extends Dispatch<infer Action> ? Action : never;
type ReactDynamicMiddlewareInstance<State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = DynamicMiddlewareInstance<State, DispatchType> & {
createDispatchWithMiddlewareHookFactory: (context?: Context<ReactReduxContextValue<State, ActionFromDispatch<DispatchType>> | null>) => CreateDispatchWithMiddlewareHook<State, DispatchType>;
createDispatchWithMiddlewareHook: CreateDispatchWithMiddlewareHook<State, DispatchType>;
};
declare const createDynamicMiddleware: <State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>>() => ReactDynamicMiddlewareInstance<State, DispatchType>;
export { type CreateDispatchWithMiddlewareHook, createDynamicMiddleware };

View file

@ -0,0 +1,22 @@
import { TSHelpersExtractDispatchExtensions, MiddlewareApiConfig, GetState, GetDispatch, DynamicMiddlewareInstance } from '@reduxjs/toolkit';
export * from '@reduxjs/toolkit';
import { Context } from 'react';
import { ReactReduxContextValue } from 'react-redux';
import { Dispatch, UnknownAction, Middleware, Action } from 'redux';
type UseDispatchWithMiddlewareHook<Middlewares extends Middleware<any, State, DispatchType>[] = [], State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = () => TSHelpersExtractDispatchExtensions<Middlewares> & DispatchType;
type CreateDispatchWithMiddlewareHook<State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = {
<Middlewares extends [
Middleware<any, State, DispatchType>,
...Middleware<any, State, DispatchType>[]
]>(...middlewares: Middlewares): UseDispatchWithMiddlewareHook<Middlewares, State, DispatchType>;
withTypes<MiddlewareConfig extends MiddlewareApiConfig>(): CreateDispatchWithMiddlewareHook<GetState<MiddlewareConfig>, GetDispatch<MiddlewareConfig>>;
};
type ActionFromDispatch<DispatchType extends Dispatch<Action>> = DispatchType extends Dispatch<infer Action> ? Action : never;
type ReactDynamicMiddlewareInstance<State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = DynamicMiddlewareInstance<State, DispatchType> & {
createDispatchWithMiddlewareHookFactory: (context?: Context<ReactReduxContextValue<State, ActionFromDispatch<DispatchType>> | null>) => CreateDispatchWithMiddlewareHook<State, DispatchType>;
createDispatchWithMiddlewareHook: CreateDispatchWithMiddlewareHook<State, DispatchType>;
};
declare const createDynamicMiddleware: <State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>>() => ReactDynamicMiddlewareInstance<State, DispatchType>;
export { type CreateDispatchWithMiddlewareHook, createDynamicMiddleware };

View file

@ -0,0 +1,2 @@
export*from"@reduxjs/toolkit";import{createDynamicMiddleware as p}from"@reduxjs/toolkit";import{createDispatchHook as d,ReactReduxContext as c,useDispatch as s}from"react-redux";var h=()=>{let t=p(),a=(i=c)=>{let o=i===c?s:d(i);function e(...r){return t.addMiddleware(...r),o}return e.withTypes=()=>e,e},n=a();return{...t,createDispatchWithMiddlewareHookFactory:a,createDispatchWithMiddlewareHook:n}};export{h as createDynamicMiddleware};
//# sourceMappingURL=redux-toolkit-react.browser.mjs.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../src/react/index.ts","../../src/dynamicMiddleware/react/index.ts"],"sourcesContent":["// This must remain here so that the `mangleErrors.cjs` build script\n// does not have to import this into each source file it rewrites.\nimport { formatProdErrorMessage } from '@reduxjs/toolkit';\nexport * from '@reduxjs/toolkit';\nexport { createDynamicMiddleware } from '../dynamicMiddleware/react';\nexport type { CreateDispatchWithMiddlewareHook } from '../dynamicMiddleware/react/index';","import type { DynamicMiddlewareInstance, GetDispatch, GetState, MiddlewareApiConfig, TSHelpersExtractDispatchExtensions } from '@reduxjs/toolkit';\nimport { createDynamicMiddleware as cDM } from '@reduxjs/toolkit';\nimport type { Context } from 'react';\nimport type { ReactReduxContextValue } from 'react-redux';\nimport { createDispatchHook, ReactReduxContext, useDispatch as useDefaultDispatch } from 'react-redux';\nimport type { Action, Dispatch, Middleware, UnknownAction } from 'redux';\nexport type UseDispatchWithMiddlewareHook<Middlewares extends Middleware<any, State, DispatchType>[] = [], State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = () => TSHelpersExtractDispatchExtensions<Middlewares> & DispatchType;\nexport type CreateDispatchWithMiddlewareHook<State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = {\n <Middlewares extends [Middleware<any, State, DispatchType>, ...Middleware<any, State, DispatchType>[]]>(...middlewares: Middlewares): UseDispatchWithMiddlewareHook<Middlewares, State, DispatchType>;\n withTypes<MiddlewareConfig extends MiddlewareApiConfig>(): CreateDispatchWithMiddlewareHook<GetState<MiddlewareConfig>, GetDispatch<MiddlewareConfig>>;\n};\ntype ActionFromDispatch<DispatchType extends Dispatch<Action>> = DispatchType extends Dispatch<infer Action> ? Action : never;\ntype ReactDynamicMiddlewareInstance<State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = DynamicMiddlewareInstance<State, DispatchType> & {\n createDispatchWithMiddlewareHookFactory: (context?: Context<ReactReduxContextValue<State, ActionFromDispatch<DispatchType>> | null>) => CreateDispatchWithMiddlewareHook<State, DispatchType>;\n createDispatchWithMiddlewareHook: CreateDispatchWithMiddlewareHook<State, DispatchType>;\n};\nexport const createDynamicMiddleware = <State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>>(): ReactDynamicMiddlewareInstance<State, DispatchType> => {\n const instance = cDM<State, DispatchType>();\n const createDispatchWithMiddlewareHookFactory = (\n // @ts-ignore\n context: Context<ReactReduxContextValue<State, ActionFromDispatch<DispatchType>> | null> = ReactReduxContext) => {\n const useDispatch = context === ReactReduxContext ? useDefaultDispatch : createDispatchHook(context);\n function createDispatchWithMiddlewareHook<Middlewares extends Middleware<any, State, DispatchType>[]>(...middlewares: Middlewares) {\n instance.addMiddleware(...middlewares);\n return useDispatch;\n }\n createDispatchWithMiddlewareHook.withTypes = () => createDispatchWithMiddlewareHook;\n return createDispatchWithMiddlewareHook as CreateDispatchWithMiddlewareHook<State, DispatchType>;\n };\n const createDispatchWithMiddlewareHook = createDispatchWithMiddlewareHookFactory();\n return {\n ...instance,\n createDispatchWithMiddlewareHookFactory,\n createDispatchWithMiddlewareHook\n };\n};"],"mappings":"AAGA,WAAc,mBCFd,OAAS,2BAA2BA,MAAW,mBAG/C,OAAS,sBAAAC,EAAoB,qBAAAC,EAAmB,eAAeC,MAA0B,cAYlF,IAAMC,EAA0B,IAAgJ,CACrL,IAAMC,EAAWL,EAAyB,EACpCM,EAA0C,CAEhDC,EAA2FL,IAAsB,CAC/G,IAAMM,EAAcD,IAAYL,EAAoBC,EAAqBF,EAAmBM,CAAO,EACnG,SAASE,KAAgGC,EAA0B,CACjI,OAAAL,EAAS,cAAc,GAAGK,CAAW,EAC9BF,CACT,CACA,OAAAC,EAAiC,UAAY,IAAMA,EAC5CA,CACT,EACMA,EAAmCH,EAAwC,EACjF,MAAO,CACL,GAAGD,EACH,wCAAAC,EACA,iCAAAG,CACF,CACF","names":["cDM","createDispatchHook","ReactReduxContext","useDefaultDispatch","createDynamicMiddleware","instance","createDispatchWithMiddlewareHookFactory","context","useDispatch","createDispatchWithMiddlewareHook","middlewares"]}

View file

@ -0,0 +1,47 @@
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
// src/react/index.ts
export * from "@reduxjs/toolkit";
// src/dynamicMiddleware/react/index.ts
import { createDynamicMiddleware as cDM } from "@reduxjs/toolkit";
import { createDispatchHook, ReactReduxContext, useDispatch as useDefaultDispatch } from "react-redux";
var createDynamicMiddleware = () => {
const instance = cDM();
const createDispatchWithMiddlewareHookFactory = (context = ReactReduxContext) => {
const useDispatch = context === ReactReduxContext ? useDefaultDispatch : createDispatchHook(context);
function createDispatchWithMiddlewareHook2(...middlewares) {
instance.addMiddleware(...middlewares);
return useDispatch;
}
createDispatchWithMiddlewareHook2.withTypes = () => createDispatchWithMiddlewareHook2;
return createDispatchWithMiddlewareHook2;
};
const createDispatchWithMiddlewareHook = createDispatchWithMiddlewareHookFactory();
return __spreadProps(__spreadValues({}, instance), {
createDispatchWithMiddlewareHookFactory,
createDispatchWithMiddlewareHook
});
};
export {
createDynamicMiddleware
};
//# sourceMappingURL=redux-toolkit-react.legacy-esm.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../src/react/index.ts","../../src/dynamicMiddleware/react/index.ts"],"sourcesContent":["// This must remain here so that the `mangleErrors.cjs` build script\n// does not have to import this into each source file it rewrites.\nimport { formatProdErrorMessage } from '@reduxjs/toolkit';\nexport * from '@reduxjs/toolkit';\nexport { createDynamicMiddleware } from '../dynamicMiddleware/react';\nexport type { CreateDispatchWithMiddlewareHook } from '../dynamicMiddleware/react/index';","import type { DynamicMiddlewareInstance, GetDispatch, GetState, MiddlewareApiConfig, TSHelpersExtractDispatchExtensions } from '@reduxjs/toolkit';\nimport { createDynamicMiddleware as cDM } from '@reduxjs/toolkit';\nimport type { Context } from 'react';\nimport type { ReactReduxContextValue } from 'react-redux';\nimport { createDispatchHook, ReactReduxContext, useDispatch as useDefaultDispatch } from 'react-redux';\nimport type { Action, Dispatch, Middleware, UnknownAction } from 'redux';\nexport type UseDispatchWithMiddlewareHook<Middlewares extends Middleware<any, State, DispatchType>[] = [], State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = () => TSHelpersExtractDispatchExtensions<Middlewares> & DispatchType;\nexport type CreateDispatchWithMiddlewareHook<State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = {\n <Middlewares extends [Middleware<any, State, DispatchType>, ...Middleware<any, State, DispatchType>[]]>(...middlewares: Middlewares): UseDispatchWithMiddlewareHook<Middlewares, State, DispatchType>;\n withTypes<MiddlewareConfig extends MiddlewareApiConfig>(): CreateDispatchWithMiddlewareHook<GetState<MiddlewareConfig>, GetDispatch<MiddlewareConfig>>;\n};\ntype ActionFromDispatch<DispatchType extends Dispatch<Action>> = DispatchType extends Dispatch<infer Action> ? Action : never;\ntype ReactDynamicMiddlewareInstance<State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = DynamicMiddlewareInstance<State, DispatchType> & {\n createDispatchWithMiddlewareHookFactory: (context?: Context<ReactReduxContextValue<State, ActionFromDispatch<DispatchType>> | null>) => CreateDispatchWithMiddlewareHook<State, DispatchType>;\n createDispatchWithMiddlewareHook: CreateDispatchWithMiddlewareHook<State, DispatchType>;\n};\nexport const createDynamicMiddleware = <State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>>(): ReactDynamicMiddlewareInstance<State, DispatchType> => {\n const instance = cDM<State, DispatchType>();\n const createDispatchWithMiddlewareHookFactory = (\n // @ts-ignore\n context: Context<ReactReduxContextValue<State, ActionFromDispatch<DispatchType>> | null> = ReactReduxContext) => {\n const useDispatch = context === ReactReduxContext ? useDefaultDispatch : createDispatchHook(context);\n function createDispatchWithMiddlewareHook<Middlewares extends Middleware<any, State, DispatchType>[]>(...middlewares: Middlewares) {\n instance.addMiddleware(...middlewares);\n return useDispatch;\n }\n createDispatchWithMiddlewareHook.withTypes = () => createDispatchWithMiddlewareHook;\n return createDispatchWithMiddlewareHook as CreateDispatchWithMiddlewareHook<State, DispatchType>;\n };\n const createDispatchWithMiddlewareHook = createDispatchWithMiddlewareHookFactory();\n return {\n ...instance,\n createDispatchWithMiddlewareHookFactory,\n createDispatchWithMiddlewareHook\n };\n};"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAGA,cAAc;;;ACFd,SAAS,2BAA2B,WAAW;AAG/C,SAAS,oBAAoB,mBAAmB,eAAe,0BAA0B;AAYlF,IAAM,0BAA0B,MAAgJ;AACrL,QAAM,WAAW,IAAyB;AAC1C,QAAM,0CAA0C,CAEhD,UAA2F,sBAAsB;AAC/G,UAAM,cAAc,YAAY,oBAAoB,qBAAqB,mBAAmB,OAAO;AACnG,aAASA,qCAAgG,aAA0B;AACjI,eAAS,cAAc,GAAG,WAAW;AACrC,aAAO;AAAA,IACT;AACA,IAAAA,kCAAiC,YAAY,MAAMA;AACnD,WAAOA;AAAA,EACT;AACA,QAAM,mCAAmC,wCAAwC;AACjF,SAAO,iCACF,WADE;AAAA,IAEL;AAAA,IACA;AAAA,EACF;AACF;","names":["createDispatchWithMiddlewareHook"]}

View file

@ -0,0 +1,28 @@
// src/react/index.ts
export * from "@reduxjs/toolkit";
// src/dynamicMiddleware/react/index.ts
import { createDynamicMiddleware as cDM } from "@reduxjs/toolkit";
import { createDispatchHook, ReactReduxContext, useDispatch as useDefaultDispatch } from "react-redux";
var createDynamicMiddleware = () => {
const instance = cDM();
const createDispatchWithMiddlewareHookFactory = (context = ReactReduxContext) => {
const useDispatch = context === ReactReduxContext ? useDefaultDispatch : createDispatchHook(context);
function createDispatchWithMiddlewareHook2(...middlewares) {
instance.addMiddleware(...middlewares);
return useDispatch;
}
createDispatchWithMiddlewareHook2.withTypes = () => createDispatchWithMiddlewareHook2;
return createDispatchWithMiddlewareHook2;
};
const createDispatchWithMiddlewareHook = createDispatchWithMiddlewareHookFactory();
return {
...instance,
createDispatchWithMiddlewareHookFactory,
createDispatchWithMiddlewareHook
};
};
export {
createDynamicMiddleware
};
//# sourceMappingURL=redux-toolkit-react.modern.mjs.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["../../src/react/index.ts","../../src/dynamicMiddleware/react/index.ts"],"sourcesContent":["// This must remain here so that the `mangleErrors.cjs` build script\n// does not have to import this into each source file it rewrites.\nimport { formatProdErrorMessage } from '@reduxjs/toolkit';\nexport * from '@reduxjs/toolkit';\nexport { createDynamicMiddleware } from '../dynamicMiddleware/react';\nexport type { CreateDispatchWithMiddlewareHook } from '../dynamicMiddleware/react/index';","import type { DynamicMiddlewareInstance, GetDispatch, GetState, MiddlewareApiConfig, TSHelpersExtractDispatchExtensions } from '@reduxjs/toolkit';\nimport { createDynamicMiddleware as cDM } from '@reduxjs/toolkit';\nimport type { Context } from 'react';\nimport type { ReactReduxContextValue } from 'react-redux';\nimport { createDispatchHook, ReactReduxContext, useDispatch as useDefaultDispatch } from 'react-redux';\nimport type { Action, Dispatch, Middleware, UnknownAction } from 'redux';\nexport type UseDispatchWithMiddlewareHook<Middlewares extends Middleware<any, State, DispatchType>[] = [], State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = () => TSHelpersExtractDispatchExtensions<Middlewares> & DispatchType;\nexport type CreateDispatchWithMiddlewareHook<State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = {\n <Middlewares extends [Middleware<any, State, DispatchType>, ...Middleware<any, State, DispatchType>[]]>(...middlewares: Middlewares): UseDispatchWithMiddlewareHook<Middlewares, State, DispatchType>;\n withTypes<MiddlewareConfig extends MiddlewareApiConfig>(): CreateDispatchWithMiddlewareHook<GetState<MiddlewareConfig>, GetDispatch<MiddlewareConfig>>;\n};\ntype ActionFromDispatch<DispatchType extends Dispatch<Action>> = DispatchType extends Dispatch<infer Action> ? Action : never;\ntype ReactDynamicMiddlewareInstance<State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>> = DynamicMiddlewareInstance<State, DispatchType> & {\n createDispatchWithMiddlewareHookFactory: (context?: Context<ReactReduxContextValue<State, ActionFromDispatch<DispatchType>> | null>) => CreateDispatchWithMiddlewareHook<State, DispatchType>;\n createDispatchWithMiddlewareHook: CreateDispatchWithMiddlewareHook<State, DispatchType>;\n};\nexport const createDynamicMiddleware = <State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>>(): ReactDynamicMiddlewareInstance<State, DispatchType> => {\n const instance = cDM<State, DispatchType>();\n const createDispatchWithMiddlewareHookFactory = (\n // @ts-ignore\n context: Context<ReactReduxContextValue<State, ActionFromDispatch<DispatchType>> | null> = ReactReduxContext) => {\n const useDispatch = context === ReactReduxContext ? useDefaultDispatch : createDispatchHook(context);\n function createDispatchWithMiddlewareHook<Middlewares extends Middleware<any, State, DispatchType>[]>(...middlewares: Middlewares) {\n instance.addMiddleware(...middlewares);\n return useDispatch;\n }\n createDispatchWithMiddlewareHook.withTypes = () => createDispatchWithMiddlewareHook;\n return createDispatchWithMiddlewareHook as CreateDispatchWithMiddlewareHook<State, DispatchType>;\n };\n const createDispatchWithMiddlewareHook = createDispatchWithMiddlewareHookFactory();\n return {\n ...instance,\n createDispatchWithMiddlewareHookFactory,\n createDispatchWithMiddlewareHook\n };\n};"],"mappings":";AAGA,cAAc;;;ACFd,SAAS,2BAA2B,WAAW;AAG/C,SAAS,oBAAoB,mBAAmB,eAAe,0BAA0B;AAYlF,IAAM,0BAA0B,MAAgJ;AACrL,QAAM,WAAW,IAAyB;AAC1C,QAAM,0CAA0C,CAEhD,UAA2F,sBAAsB;AAC/G,UAAM,cAAc,YAAY,oBAAoB,qBAAqB,mBAAmB,OAAO;AACnG,aAASA,qCAAgG,aAA0B;AACjI,eAAS,cAAc,GAAG,WAAW;AACrC,aAAO;AAAA,IACT;AACA,IAAAA,kCAAiC,YAAY,MAAMA;AACnD,WAAOA;AAAA,EACT;AACA,QAAM,mCAAmC,wCAAwC;AACjF,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA;AAAA,EACF;AACF;","names":["createDispatchWithMiddlewareHook"]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,16 @@
// inlined from https://github.com/EskiMojo14/uncheckedindexed
// relies on remaining as a TS file, not .d.ts
type IfMaybeUndefined<T, True, False> = [undefined] extends [T] ? True : False
const testAccess = ({} as Record<string, 0>)['a']
export type IfUncheckedIndexedAccess<True, False> = IfMaybeUndefined<
typeof testAccess,
True,
False
>
export type UncheckedIndexedAccess<T> = IfUncheckedIndexedAccess<
T | undefined,
T
>

285
frontend/node_modules/@reduxjs/toolkit/package.json generated vendored Normal file
View file

@ -0,0 +1,285 @@
{
"name": "@reduxjs/toolkit",
"version": "2.12.0",
"description": "The official, opinionated, batteries-included toolset for efficient Redux development",
"author": "Mark Erikson <mark@isquaredsoftware.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/reduxjs/redux-toolkit.git"
},
"keywords": [
"redux",
"react",
"starter",
"toolkit",
"reducer",
"slice",
"immer",
"immutable",
"redux-toolkit",
"tanstack-intent"
],
"publishConfig": {
"access": "public"
},
"module": "dist/redux-toolkit.legacy-esm.js",
"main": "dist/cjs/index.js",
"types": "dist/index.d.ts",
"react-native": "dist/redux-toolkit.legacy-esm.js",
"unpkg": "dist/redux-toolkit.browser.mjs",
"exports": {
"./package.json": "./package.json",
".": {
"module-sync": {
"types": "./dist/index.d.mts",
"default": "./dist/redux-toolkit.modern.mjs"
},
"module": {
"types": "./dist/index.d.mts",
"default": "./dist/redux-toolkit.modern.mjs"
},
"react-native": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/redux-toolkit.modern.mjs"
},
"default": {
"types": "./dist/index.d.ts",
"default": "./dist/cjs/index.js"
}
},
"browser": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/redux-toolkit.browser.mjs"
},
"default": {
"types": "./dist/index.d.ts",
"default": "./dist/cjs/index.js"
}
},
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/redux-toolkit.modern.mjs"
},
"default": {
"types": "./dist/index.d.ts",
"default": "./dist/cjs/index.js"
}
},
"./react": {
"module-sync": {
"types": "./dist/react/index.d.mts",
"default": "./dist/react/redux-toolkit-react.modern.mjs"
},
"module": {
"types": "./dist/react/index.d.mts",
"default": "./dist/react/redux-toolkit-react.modern.mjs"
},
"react-native": {
"import": {
"types": "./dist/react/index.d.mts",
"default": "./dist/react/redux-toolkit-react.modern.mjs"
},
"default": {
"types": "./dist/react/index.d.ts",
"default": "./dist/react/cjs/index.js"
}
},
"browser": {
"import": {
"types": "./dist/react/index.d.mts",
"default": "./dist/react/redux-toolkit-react.browser.mjs"
},
"default": {
"types": "./dist/react/index.d.ts",
"default": "./dist/react/cjs/index.js"
}
},
"import": {
"types": "./dist/react/index.d.mts",
"default": "./dist/react/redux-toolkit-react.modern.mjs"
},
"default": {
"types": "./dist/react/index.d.ts",
"default": "./dist/react/cjs/index.js"
}
},
"./query": {
"module-sync": {
"types": "./dist/query/index.d.mts",
"default": "./dist/query/rtk-query.modern.mjs"
},
"module": {
"types": "./dist/query/index.d.mts",
"default": "./dist/query/rtk-query.modern.mjs"
},
"react-native": {
"import": {
"types": "./dist/query/index.d.mts",
"default": "./dist/query/rtk-query.modern.mjs"
},
"default": {
"types": "./dist/query/index.d.ts",
"default": "./dist/query/cjs/index.js"
}
},
"browser": {
"import": {
"types": "./dist/query/index.d.mts",
"default": "./dist/query/rtk-query.browser.mjs"
},
"default": {
"types": "./dist/query/index.d.ts",
"default": "./dist/query/cjs/index.js"
}
},
"import": {
"types": "./dist/query/index.d.mts",
"default": "./dist/query/rtk-query.modern.mjs"
},
"default": {
"types": "./dist/query/index.d.ts",
"default": "./dist/query/cjs/index.js"
}
},
"./query/react": {
"module-sync": {
"types": "./dist/query/react/index.d.mts",
"default": "./dist/query/react/rtk-query-react.modern.mjs"
},
"module": {
"types": "./dist/query/react/index.d.mts",
"default": "./dist/query/react/rtk-query-react.modern.mjs"
},
"react-native": {
"import": {
"types": "./dist/query/react/index.d.mts",
"default": "./dist/query/react/rtk-query-react.modern.mjs"
},
"default": {
"types": "./dist/query/react/index.d.ts",
"default": "./dist/query/react/cjs/index.js"
}
},
"browser": {
"import": {
"types": "./dist/query/react/index.d.mts",
"default": "./dist/query/react/rtk-query-react.browser.mjs"
},
"default": {
"types": "./dist/query/react/index.d.ts",
"default": "./dist/query/react/cjs/index.js"
}
},
"import": {
"types": "./dist/query/react/index.d.mts",
"default": "./dist/query/react/rtk-query-react.modern.mjs"
},
"default": {
"types": "./dist/query/react/index.d.ts",
"default": "./dist/query/react/cjs/index.js"
}
}
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.18.2",
"@babel/core": "^7.24.8",
"@babel/helper-module-imports": "^7.24.7",
"@microsoft/api-extractor": "^7.13.2",
"@phryneas/ts-version": "^1.0.2",
"@size-limit/file": "^11.0.1",
"@size-limit/webpack": "^11.0.1",
"@tanstack/intent": "^0.0.19",
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.0.1",
"@testing-library/react-render-stream": "^1.0.3",
"@testing-library/user-event": "^14.5.2",
"@types/babel__core": "^7.20.5",
"@types/babel__helper-module-imports": "^7.18.3",
"@types/node": "^25.5.0",
"@types/query-string": "^6.3.0",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.1",
"@typescript-eslint/eslint-plugin": "^6",
"@typescript-eslint/parser": "^6",
"axios": "^0.19.2",
"esbuild": "^0.25.1",
"esbuild-extra": "^0.4.0",
"eslint": "^7.25.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-flowtype": "^5.7.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.2.0",
"jsdom": "^25.0.1",
"msw": "^2.1.4",
"node-fetch": "^3.3.2",
"prettier": "^3.2.5",
"query-string": "^7.0.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-redux": "^9.2.0",
"rimraf": "^6.1.3",
"size-limit": "^11.0.1",
"tsup": "^8.4.0",
"tsx": "^4.19.0",
"typescript": "^5.9.3",
"valibot": "^1.0.0",
"vite-tsconfig-paths": "^4.3.1",
"vitest": "^4"
},
"scripts": {
"clean": "rimraf dist",
"run-build": "tsup --config=$INIT_CWD/tsup.config.mts",
"build": "yarn clean && yarn run-build && tsx scripts/fixUniqueSymbolExports.mts",
"build-only": "yarn clean && yarn run-build",
"format": "prettier --write \"(src|examples)/**/*.{ts,tsx}\" \"**/*.md\"",
"format:check": "prettier --list-different \"(src|examples)/**/*.{ts,tsx}\" \"docs/*/**.md\"",
"lint": "eslint src examples",
"test": "vitest --typecheck --run ",
"test:watch": "vitest --watch",
"type-tests": "yarn tsc -p tsconfig.test.json --noEmit",
"prepack": "yarn build",
"size": "size-limit"
},
"files": [
"dist/",
"src/",
"!src/**/tests/**",
"!src/**/*.{test,spec}(-d)?.(c|m)[tj]sx?",
"query",
"react",
"skills",
"!skills/_artifacts"
],
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@standard-schema/utils": "^0.3.0",
"immer": "^11.0.0",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"reselect": "^5.1.0"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-redux": {
"optional": true
}
},
"sideEffects": false,
"bugs": {
"url": "https://github.com/reduxjs/redux-toolkit/issues"
},
"homepage": "https://redux-toolkit.js.org"
}

View file

@ -0,0 +1,13 @@
{
"name": "@reduxjs/toolkit-query",
"version": "1.0.0",
"description": "",
"type": "module",
"module": "../dist/query/rtk-query.legacy-esm.js",
"main": "../dist/query/cjs/index.js",
"types": "./../dist/query/index.d.ts",
"react-native": "./../dist/query/rtk-query.legacy-esm.js",
"author": "Mark Erikson <mark@isquaredsoftware.com>",
"license": "MIT",
"sideEffects": false
}

View file

@ -0,0 +1,13 @@
{
"name": "@reduxjs/toolkit-query-react",
"version": "1.0.0",
"description": "",
"type": "module",
"module": "../../dist/query/react/rtk-query-react.legacy-esm.js",
"main": "../../dist/query/react/cjs/index.js",
"types": "./../../dist/query/react/index.d.ts",
"react-native": "./../../dist/query/react/rtk-query-react.legacy-esm.js",
"author": "Mark Erikson <mark@isquaredsoftware.com>",
"license": "MIT",
"sideEffects": false
}

View file

@ -0,0 +1,13 @@
{
"name": "@reduxjs/toolkit-react",
"version": "1.0.0",
"description": "",
"type": "module",
"module": "../dist/react/redux-toolkit-react.legacy-esm.js",
"main": "./../dist/react/redux-toolkit-react.modern.mjs",
"types": "./../dist/react/index.d.ts",
"react-native": "./../dist/react/redux-toolkit-react.modern.mjs",
"author": "Mark Erikson <mark@isquaredsoftware.com>",
"license": "MIT",
"sideEffects": false
}

View file

@ -0,0 +1,304 @@
---
name: build-modern-redux-apps/modern-redux
description: >
Use this when setting up a new Redux Toolkit app or modernizing an existing
React + Redux codebase. Covers configureStore, Provider wiring, typed hooks,
hooks-first React-Redux usage, feature folders, and the correct store lifetime
for SPA and SSR-heavy React environments.
type: lifecycle
library: "@reduxjs/toolkit"
library_version: "2.11.2"
requires:
- build-modern-redux-apps/redux-dataflow
sources:
- "reduxjs/redux-toolkit:docs/tutorials/quick-start.mdx"
- "reduxjs/redux-toolkit:docs/tutorials/typescript.md"
- "reduxjs/redux-toolkit:docs/usage/migrating-to-modern-redux.mdx"
- "reduxjs/redux-toolkit:docs/usage/nextjs.mdx"
- "reduxjs/redux:docs/style-guide/style-guide.md"
---
# Modern Redux
## Setup
```tsx
// file: src/features/counter/counterSlice.ts
import { createSlice } from '@reduxjs/toolkit'
export const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment(state) {
state.value += 1
},
},
})
export const { increment } = counterSlice.actions
// file: src/app/store.ts
import { configureStore } from '@reduxjs/toolkit'
import { counterSlice } from '../features/counter/counterSlice'
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
// file: src/app/hooks.ts
import { useDispatch, useSelector } from 'react-redux'
import type { AppDispatch, RootState } from './store'
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
// file: src/features/counter/Counter.tsx
import { increment } from './counterSlice'
import { useAppDispatch, useAppSelector } from '../../app/hooks'
export function Counter() {
const value = useAppSelector((state) => state.counter.value)
const dispatch = useAppDispatch()
return <button onClick={() => dispatch(increment())}>{value}</button>
}
// file: src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import { store } from './app/store'
import { Counter } from './features/counter/Counter'
ReactDOM.createRoot(document.getElementById('root')!).render(
<Provider store={store}>
<Counter />
</Provider>,
)
```
## Core Patterns
### Keep React components on hooks, not wrappers
```tsx
import { postAdded, selectPosts } from './postsSlice'
import { useAppDispatch, useAppSelector } from '../../app/hooks'
export function PostsList() {
const posts = useAppSelector(selectPosts)
const dispatch = useAppDispatch()
return (
<button
onClick={() =>
dispatch(postAdded({ id: 'p2', title: 'Write docs' }))
}
>
{posts.length}
</button>
)
}
```
Hooks are the default React-Redux integration for new code.
### Create the store inside the provider for SSR-heavy React apps
```tsx
// file: src/lib/store.ts
import { configureStore } from '@reduxjs/toolkit'
import { counterSlice } from '../features/counter/counterSlice'
export const makeStore = () =>
configureStore({
reducer: {
counter: counterSlice.reducer,
},
})
export type AppStore = ReturnType<typeof makeStore>
export type RootState = ReturnType<AppStore['getState']>
export type AppDispatch = AppStore['dispatch']
// file: src/app/StoreProvider.tsx
'use client'
import { useState, type ReactNode } from 'react'
import { Provider } from 'react-redux'
import { makeStore } from '../lib/store'
export function StoreProvider({ children }: { children: ReactNode }) {
const [store] = useState(makeStore)
return <Provider store={store}>{children}</Provider>
}
```
In SSR-heavy React frameworks, create a store per request and keep that instance stable across renders.
### Keep app wiring in `app/` and feature logic in feature folders
```text
src/
app/
store.ts
hooks.ts
features/
posts/
postsSlice.ts
PostsList.tsx
users/
usersSlice.ts
UsersList.tsx
```
This keeps store wiring centralized and feature logic colocated.
## Common Mistakes
### HIGH Importing the store in React components
Wrong:
```tsx
import { store } from '../../app/store'
export function PostsList() {
const posts = store.getState().posts
return <div>{posts.length}</div>
}
```
Correct:
```tsx
import { useAppSelector } from '../../app/hooks'
export function PostsList() {
const posts = useAppSelector((state) => state.posts)
return <div>{posts.length}</div>
}
```
React components should read through context and hooks; direct store imports are a separate escape hatch for non-React integrations, not the default UI pattern.
Source: reduxjs/redux:docs/style-guide/style-guide.md
### HIGH Defaulting to `connect()` in new React code
Wrong:
```tsx
import { connect } from 'react-redux'
const increment = () => ({ type: 'counter/increment' as const })
const mapState = (state: { counter: { value: number } }) => ({
value: state.counter.value,
})
const mapDispatch = { increment }
function Counter({ value, increment }: ReturnType<typeof mapState> & typeof mapDispatch) {
return <button onClick={() => increment()}>{value}</button>
}
export default connect(mapState, mapDispatch)(Counter)
```
Correct:
```tsx
import { increment } from './counterSlice'
import { useAppDispatch, useAppSelector } from '../../app/hooks'
export function Counter() {
const value = useAppSelector((state) => state.counter.value)
const dispatch = useAppDispatch()
return <button onClick={() => dispatch(increment())}>{value}</button>
}
```
Hooks are the modern default, simpler to type, and the maintainers explicitly want agents to steer new code away from `connect`.
Source: reduxjs/redux-toolkit:docs/usage/migrating-to-modern-redux.mdx
### HIGH Recreating the store during render in SSR-heavy apps
Wrong:
```tsx
'use client'
import { Provider } from 'react-redux'
import { makeStore } from '../lib/store'
export function StoreProvider({ children }: { children: import('react').ReactNode }) {
const store = makeStore()
return <Provider store={store}>{children}</Provider>
}
```
Correct:
```tsx
'use client'
import { useState } from 'react'
import { Provider } from 'react-redux'
import { makeStore } from '../lib/store'
export function StoreProvider({ children }: { children: import('react').ReactNode }) {
const [store] = useState(makeStore)
return <Provider store={store}>{children}</Provider>
}
```
A new store on every render loses client state, while a module singleton can leak across requests on the server.
Source: reduxjs/redux-toolkit:docs/usage/nextjs.mdx
### HIGH Keeping `createStore` boilerplate as the default
Wrong:
```ts
import { applyMiddleware, combineReducers, createStore } from 'redux'
import thunk from 'redux-thunk'
const counterReducer = (state = { value: 0 }) => state
const rootReducer = combineReducers({
counter: counterReducer,
})
export const store = createStore(rootReducer, applyMiddleware(thunk))
```
Correct:
```ts
import { configureStore } from '@reduxjs/toolkit'
const counterReducer = (state = { value: 0 }) => state
export const store = configureStore({
reducer: {
counter: counterReducer,
},
})
```
Manual store setup works, but it throws away RTK's default middleware, dev checks, and the current recommended baseline.
Source: reduxjs/redux-toolkit:docs/usage/migrating-to-modern-redux.mdx
## References
- [Store lifetime and framework boundaries](references/store-lifetime.md)

View file

@ -0,0 +1,53 @@
# Store Lifetime
## Decision table
| Environment | Default store shape | Why |
| --- | --- | --- |
| Client-only SPA | One module-level singleton store | There is one browser session and no cross-request leakage risk. |
| SSR-heavy React app | `makeStore()` plus provider-local state | Each request needs its own store instance, but that instance must stay stable across client renders. |
| Non-React integration code | Direct store access can be acceptable | This is outside the React context boundary and should stay out of UI components. |
## SPA pattern
```ts
import { configureStore } from '@reduxjs/toolkit'
import { postsSlice } from '../features/posts/postsSlice'
export const store = configureStore({
reducer: {
posts: postsSlice.reducer,
},
})
```
Use this for classic browser SPAs.
## SSR-heavy React pattern
```tsx
// file: src/lib/store.ts
import { configureStore } from '@reduxjs/toolkit'
import { postsSlice } from '../features/posts/postsSlice'
export const makeStore = () =>
configureStore({
reducer: {
posts: postsSlice.reducer,
},
})
// file: src/app/StoreProvider.tsx
'use client'
import { useState, type ReactNode } from 'react'
import { Provider } from 'react-redux'
import { makeStore } from '../lib/store'
export function StoreProvider({ children }: { children: ReactNode }) {
const [store] = useState(makeStore)
return <Provider store={store}>{children}</Provider>
}
```
Create the store per request, then keep it stable inside the provider component.

View file

@ -0,0 +1,264 @@
---
name: build-modern-redux-apps/redux-dataflow
description: >
Use this when you need the Redux event -> reducer -> selector -> render loop,
event-style actions, reducer-owned state transitions, derived data, or a
debugging model for Redux Toolkit apps.
type: core
library: "@reduxjs/toolkit"
library_version: "2.11.2"
sources:
- "reduxjs/redux:docs/tutorials/fundamentals/part-2-concepts-data-flow.md"
- "reduxjs/redux:docs/tutorials/essentials/part-3-data-flow.md"
- "reduxjs/redux:docs/style-guide/style-guide.md"
---
# Redux Dataflow
## Setup
```ts
import { configureStore, createSelector, createSlice } from '@reduxjs/toolkit'
const postsSlice = createSlice({
name: 'posts',
initialState: {
items: [] as { id: string; title: string; published: boolean }[],
filter: 'all' as 'all' | 'published',
},
reducers: {
postAdded(state, action: { payload: { id: string; title: string } }) {
state.items.push({ ...action.payload, published: false })
},
postPublished(state, action: { payload: { id: string } }) {
const post = state.items.find((item) => item.id === action.payload.id)
if (post) {
post.published = true
}
},
filterChanged(state, action: { payload: 'all' | 'published' }) {
state.filter = action.payload
},
},
})
const store = configureStore({
reducer: {
posts: postsSlice.reducer,
},
})
type RootState = ReturnType<typeof store.getState>
const selectPostsState = (state: RootState) => state.posts
const selectVisiblePosts = createSelector([selectPostsState], (postsState) =>
postsState.filter === 'all'
? postsState.items
: postsState.items.filter((post) => post.published),
)
store.dispatch(postsSlice.actions.postAdded({ id: 'p1', title: 'Draft' }))
store.dispatch(postsSlice.actions.postPublished({ id: 'p1' }))
const visiblePosts = selectVisiblePosts(store.getState())
console.log(visiblePosts)
```
## Core Patterns
### Dispatch events, not setters
```ts
const postsSlice = createSlice({
name: 'posts',
initialState: [] as { id: string; title: string }[],
reducers: {
postAdded(state, action: { payload: { id: string; title: string } }) {
state.push(action.payload)
},
postRemoved(state, action: { payload: { id: string } }) {
return state.filter((post) => post.id !== action.payload.id)
},
postUpdated(
state,
action: { payload: { id: string; changes: Partial<{ title: string }> } },
) {
const post = state.find((item) => item.id === action.payload.id)
if (post && action.payload.changes.title) {
post.title = action.payload.changes.title
}
},
},
})
postsSlice.actions.postAdded({ id: 'p1', title: 'Draft' })
```
Event-style actions explain what happened in the UI instead of hiding the transition behind a generic setter.
### Let reducers combine old store data with new outside data
```ts
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'
const postsAdapter = createEntityAdapter<{ id: string; title: string }>()
const postsSlice = createSlice({
name: 'posts',
initialState: postsAdapter.getInitialState(),
reducers: {
postsReceived(state, action: { payload: { id: string; title: string }[] }) {
postsAdapter.upsertMany(state, action.payload)
},
},
})
const incomingPosts = [
{ id: 'p1', title: 'Draft' },
{ id: 'p2', title: 'Published' },
]
postsSlice.actions.postsReceived(incomingPosts)
```
If a transition mixes current store state with new external data, dispatch the new external data and let the reducer own the merge.
### Derive values with selectors instead of storing duplicates
```ts
import { createSelector } from '@reduxjs/toolkit'
const selectPosts = (state: RootState) => state.posts.items
const selectFilter = (state: RootState) => state.posts.filter
export const selectVisiblePosts = createSelector(
[selectPosts, selectFilter],
(posts, filter) =>
filter === 'all'
? posts
: posts.filter((post) => post.published),
)
```
Selectors keep a single source of truth in state while still exposing the shapes the UI needs.
## Common Mistakes
### CRITICAL Mutating selected state outside reducers
Wrong:
```ts
const post = selectPostById(store.getState(), 'p1')
if (post) {
post.title = 'Changed in place'
}
```
Correct:
```ts
store.dispatch(postUpdated({ id: 'p1', changes: { title: 'Changed in place' } }))
```
Objects read from the store are still store state; mutating them outside reducers breaks immutability and stale-render assumptions.
Source: reduxjs/redux:docs/style-guide/style-guide.md
### HIGH Using setter-style actions instead of event-style actions
Wrong:
```ts
const nextPosts = [...selectPosts(store.getState()), { id: 'p2', title: 'Write docs' }]
store.dispatch(setPosts(nextPosts))
```
Correct:
```ts
store.dispatch(postAdded({ id: 'p2', title: 'Write docs' }))
```
Actions should describe events, not ask reducers to blindly replace state with a precomputed value.
Source: reduxjs/redux:docs/style-guide/style-guide.md
### HIGH Combining store state before dispatch
Wrong:
```ts
const currentPosts = selectPosts(store.getState())
const mergedPosts = [
...currentPosts.filter(
(currentPost) =>
!incomingPosts.some((incomingPost) => incomingPost.id === currentPost.id),
),
...incomingPosts,
]
store.dispatch(postsReplaced(mergedPosts))
```
Correct:
```ts
store.dispatch(postsReceived(incomingPosts))
```
If the next state depends on current store state, the reducer should own that combination logic; only authoritative external snapshots should replace state wholesale.
Source: maintainer interview
### HIGH Ignoring current state in async reducers
Wrong:
```ts
builder.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = 'succeeded'
state.items = action.payload
})
```
Correct:
```ts
builder.addCase(fetchPosts.fulfilled, (state, action) => {
if (state.status === 'pending') {
state.status = 'succeeded'
state.items = action.payload
}
})
```
Reducers that treat every lifecycle action as valid can move the slice into impossible states or let stale requests win.
Source: reduxjs/redux:docs/tutorials/essentials/part-5-async-logic.md
### MEDIUM Storing derived values in state
Wrong:
```ts
const initialState = {
items: [] as Post[],
visiblePosts: [] as Post[],
}
```
Correct:
```ts
const selectVisiblePosts = createSelector(
[selectPosts, selectFilter],
(posts, filter) =>
filter === 'all' ? posts : posts.filter((post) => post.published),
)
```
Derived values drift out of sync quickly; keep the raw state and derive the view shape.
Source: reduxjs/redux:docs/style-guide/style-guide.md

View file

@ -0,0 +1,269 @@
---
name: evolve-and-diagnose-redux-apps/debug-redux-toolkit-apps
description: >
Use this when debugging duplicate requests, stale cache behavior, broad
subscriptions, selector churn, serializability warnings, or other Redux
Toolkit and RTK Query bugs. Covers a practical event -> reducer -> selector ->
render debugging loop plus RTK Query cache interpretation.
type: lifecycle
library: "@reduxjs/toolkit"
library_version: "2.11.2"
requires:
- build-modern-redux-apps/redux-dataflow
sources:
- "reduxjs/redux:docs/style-guide/style-guide.md"
- "reduxjs/redux:docs/tutorials/essentials/part-5-async-logic.md"
- "reduxjs/redux:docs/tutorials/essentials/part-8-rtk-query-advanced.md"
- "reduxjs/redux-toolkit:docs/usage/usage-guide.md"
---
# Debug Redux Toolkit Apps
## Setup
```ts
import { configureStore, createAsyncThunk, createSlice } from '@reduxjs/toolkit'
type Post = { id: string; title: string }
export const fetchPosts = createAsyncThunk(
'posts/fetchPosts',
async () => {
const response = await fetch('/api/posts')
return (await response.json()) as Post[]
},
{
condition(_arg, { getState }) {
const state = getState() as RootState
return state.posts.status === 'idle'
},
},
)
const postsSlice = createSlice({
name: 'posts',
initialState: {
items: [] as Post[],
status: 'idle' as 'idle' | 'pending' | 'succeeded' | 'failed',
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchPosts.pending, (state) => {
state.status = 'pending'
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = 'succeeded'
state.items = action.payload
})
},
})
export const store = configureStore({
reducer: {
posts: postsSlice.reducer,
},
})
type RootState = ReturnType<typeof store.getState>
```
## Core Patterns
### Debug in order: action -> reducer -> selector -> render
```ts
const selectPosts = (state: RootState) => state.posts.items
const selectPostsStatus = (state: RootState) => state.posts.status
store.dispatch(fetchPosts())
console.log(selectPostsStatus(store.getState()))
console.log(selectPosts(store.getState()))
```
If a component looks wrong, first verify the action fired, then the reducer state, then the selector result, then the render boundary.
### Narrow subscriptions at the usage site
```tsx
import { useAppSelector } from '../../app/hooks'
export function PostsList() {
const posts = useAppSelector((state) => state.posts.items)
const status = useAppSelector((state) => state.posts.status)
return (
<div>
<div>{status}</div>
<div>{posts.length}</div>
</div>
)
}
```
React-Redux behaves best when components select only the values they render and do it as close to usage as possible.
### Interpret RTK Query invalidation correctly
```ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
type Post = { id: string; title: string }
const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
tagTypes: ['Post'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => 'posts',
providesTags: ['Post'],
}),
updatePost: build.mutation<Post, Pick<Post, 'id' | 'title'>>({
query: ({ id, title }) => ({
url: `posts/${id}`,
method: 'PATCH',
body: { title },
}),
invalidatesTags: ['Post'],
}),
}),
})
```
If invalidation did not visibly refetch, check whether anything was still subscribed to that cache entry.
## Common Mistakes
### HIGH Dispatching fetch thunks from effects without a thunk-level guard
Wrong:
```tsx
import { useEffect } from 'react'
import { useAppDispatch, useAppSelector } from '../../app/hooks'
function PostsPage() {
const dispatch = useAppDispatch()
const postStatus = useAppSelector((state) => state.posts.status)
useEffect(() => {
if (postStatus === 'idle') {
dispatch(fetchPosts())
}
}, [dispatch, postStatus])
return null
}
```
Correct:
```ts
export const fetchPosts = createAsyncThunk(
'posts/fetchPosts',
async () => {
const response = await fetch('/api/posts')
return (await response.json()) as Post[]
},
{
condition(_arg, { getState }) {
const state = getState() as RootState
return state.posts.status === 'idle'
},
},
)
```
React StrictMode can run effects twice in development, so the guard belongs in the thunk as well as the component.
Source: reduxjs/redux:docs/tutorials/essentials/part-5-async-logic.md
### HIGH Ignoring serializable-state warnings
Wrong:
```ts
const initialState = {
lastSeen: new Date(),
pendingIds: new Set<string>(),
}
```
Correct:
```ts
const initialState = {
lastSeenIso: new Date().toISOString(),
pendingIds: [] as string[],
}
```
Non-serializable values break DevTools, replay, persistence, and equality assumptions in subtle ways.
Source: reduxjs/redux:docs/style-guide/style-guide.md
### HIGH Selecting broad state in parents and threading props
Wrong:
```tsx
import { useAppSelector } from '../../app/hooks'
function PostsPage() {
const postsState = useAppSelector((state) => state.posts)
return <PostsList items={postsState.items} status={postsState.status} />
}
```
Correct:
```tsx
import { useAppSelector } from '../../app/hooks'
function PostsList() {
const items = useAppSelector((state) => state.posts.items)
const status = useAppSelector((state) => state.posts.status)
return (
<div>
<div>{status}</div>
<div>{items.length}</div>
</div>
)
}
```
Selecting whole slices high in the tree widens the subscription surface and pushes rerenders through props.
Source: reduxjs/redux:docs/style-guide/style-guide.md
### MEDIUM Returning unstable objects from query selection logic
Wrong:
```tsx
import { api } from '../../services/api'
const result = api.useGetPostsQuery(undefined, {
selectFromResult: ({ data = [] }) => ({
posts: [...data],
}),
})
```
Correct:
```tsx
import { api } from '../../services/api'
const result = api.useGetPostsQuery(undefined, {
selectFromResult: ({ data = [] }) => ({
posts: data,
}),
})
```
New object and array references defeat memoization and make components rerender even when the underlying cached data did not change.
Source: reduxjs/redux:docs/tutorials/essentials/part-8-rtk-query-advanced.md

View file

@ -0,0 +1,226 @@
---
name: evolve-and-diagnose-redux-apps/migrate-to-modern-redux
description: >
Use this when moving a legacy Redux codebase to current RTK patterns. Covers
replacing createStore with configureStore, migrating touched reducers to
createSlice, codemod-assisted RTK 2 updates, and replacing server-data stacks
with RTK Query instead of writing new legacy Redux code.
type: lifecycle
library: "@reduxjs/toolkit"
library_version: "2.11.2"
requires:
- build-modern-redux-apps/modern-redux
sources:
- "reduxjs/redux-toolkit:docs/usage/migrating-to-modern-redux.mdx"
- "reduxjs/redux-toolkit:docs/usage/migrating-rtk-2.md"
- "reduxjs/redux-toolkit:packages/rtk-codemods/README.md"
- "reduxjs/redux:docs/style-guide/style-guide.md"
---
# Migrate To Modern Redux
## Setup
```ts
// before
import { applyMiddleware, combineReducers, createStore } from 'redux'
import thunk from 'redux-thunk'
const postsReducer = (state = [] as { id: string; title: string }[]) => state
const usersReducer = (state = [] as { id: string; name: string }[]) => state
const rootReducer = combineReducers({
posts: postsReducer,
users: usersReducer,
})
export const legacyStore = createStore(rootReducer, applyMiddleware(thunk))
// after
import { configureStore } from '@reduxjs/toolkit'
const postsReducer = (state = [] as { id: string; title: string }[]) => state
const usersReducer = (state = [] as { id: string; name: string }[]) => state
export const store = configureStore({
reducer: {
posts: postsReducer,
users: usersReducer,
},
})
```
## Core Patterns
### Replace the store setup first
```ts
import { configureStore } from '@reduxjs/toolkit'
const postsReducer = (state = [] as { id: string; title: string }[]) => state
const usersReducer = (state = [] as { id: string; name: string }[]) => state
export const store = configureStore({
reducer: {
posts: postsReducer,
users: usersReducer,
},
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
```
This is the one migration step that can happen immediately while old reducers continue to work.
### Migrate reducers as you touch them
```ts
import { createSlice } from '@reduxjs/toolkit'
type TodosState = {
items: { id: string; text: string; completed: boolean }[]
}
const initialState: TodosState = {
items: [],
}
export const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {
todoAdded(state, action: { payload: { id: string; text: string } }) {
state.items.push({ ...action.payload, completed: false })
},
todoToggled(state, action: { payload: { id: string } }) {
const todo = state.items.find((item) => item.id === action.payload.id)
if (todo) {
todo.completed = !todo.completed
}
},
},
})
```
Once a reducer needs editing, migrate that reducer instead of adding more legacy code to it.
### Use codemods for mechanical updates
```bash
npx @reduxjs/rtk-codemods createSliceBuilder src/features/posts/postsSlice.ts
npx @reduxjs/rtk-codemods createReducerBuilder src/features/posts/postsReducer.ts
```
Use codemods for repetitive RTK API migrations, then review the result and finish the semantic cleanup by hand.
### Replace legacy server-data stacks with RTK Query
```ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
type Todo = { id: string; text: string }
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
endpoints: (build) => ({
getTodos: build.query<Todo[], void>({
query: () => 'todos',
}),
}),
})
```
When the old code is just request status plus fetched data, migrate toward RTK Query instead of carrying the thunk stack forward forever.
## Common Mistakes
### HIGH Attempting a big-bang rewrite
Wrong:
```ts
// Replace every reducer, every connected component, and every async flow
// in one branch before shipping anything.
```
Correct:
```ts
// 1) Switch createStore to configureStore
// 2) Migrate one touched reducer to createSlice
// 3) Convert touched connected components to hooks
// 4) Repeat without introducing new legacy Redux code
```
Modern Redux migration is incremental, but once the store is modernized new work should stop adding legacy patterns.
Source: reduxjs/redux-toolkit:docs/usage/migrating-to-modern-redux.mdx
### CRITICAL Carrying removed RTK 2 config forms forward
Wrong:
```ts
configureStore({
reducer,
middleware: [logger],
})
```
Correct:
```ts
configureStore({
reducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(logger),
})
```
RTK 2 removed array middleware configuration and other older builder forms that agents trained on RTK 1.x still emit.
Source: reduxjs/redux-toolkit:docs/usage/migrating-rtk-2.md
### HIGH Preserving hand-written fetch state by default
Wrong:
```ts
import { createAsyncThunk } from '@reduxjs/toolkit'
type Todo = { id: string; text: string }
const initialState = {
items: [] as Todo[],
status: 'idle' as 'idle' | 'pending' | 'failed',
}
export const fetchTodos = createAsyncThunk('todos/fetch', async () => {
const response = await fetch('/api/todos')
return (await response.json()) as Todo[]
})
```
Correct:
```ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
type Todo = { id: string; text: string }
const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
endpoints: (build) => ({
getTodos: build.query<Todo[], void>({
query: () => 'todos',
}),
}),
})
```
If the feature is really server cache, keep the migration moving toward RTK Query instead of rebuilding the old loading-flag architecture in new APIs.
Source: reduxjs/redux-toolkit:docs/usage/migrating-to-modern-redux.mdx

View file

@ -0,0 +1,385 @@
---
name: manage-server-data/adopt-rtk-query
description: >
Use this when adding RTK Query as the default server-data and document-cache
layer. Covers createApi, store integration, hooks, invalidation behavior,
optimistic updates, and deciding when RTK Query is the right cache model.
type: lifecycle
library: "@reduxjs/toolkit"
library_version: "2.11.2"
requires:
- build-modern-redux-apps/modern-redux
sources:
- "reduxjs/redux-toolkit:docs/rtk-query/api/createApi.mdx"
- "reduxjs/redux-toolkit:docs/rtk-query/usage/automated-refetching.mdx"
- "reduxjs/redux-toolkit:docs/rtk-query/usage/manual-cache-updates.mdx"
- "reduxjs/redux-toolkit:docs/rtk-query/usage/persistence-and-rehydration.mdx"
- "reduxjs/redux-toolkit:docs/tutorials/rtk-query.mdx"
- "reduxjs/redux:docs/style-guide/style-guide.md"
---
# Adopt RTK Query
## Setup
```tsx
// file: src/services/api.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
type Post = { id: string; title: string }
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
tagTypes: ['Post'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => 'posts',
providesTags: (result) =>
result
? [...result.map(({ id }) => ({ type: 'Post' as const, id })), 'Post']
: ['Post'],
}),
addPost: build.mutation<Post, Pick<Post, 'title'>>({
query: (body) => ({
url: 'posts',
method: 'POST',
body,
}),
invalidatesTags: ['Post'],
}),
}),
})
export const { useGetPostsQuery, useAddPostMutation } = api
// file: src/app/store.ts
import { configureStore } from '@reduxjs/toolkit'
import { api } from '../services/api'
export const store = configureStore({
reducer: {
[api.reducerPath]: api.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware),
})
// file: src/App.tsx
import { Provider } from 'react-redux'
import { store } from './app/store'
import { useAddPostMutation, useGetPostsQuery } from './services/api'
function Posts() {
const { data: posts = [] } = useGetPostsQuery()
const [addPost] = useAddPostMutation()
return (
<div>
<button onClick={() => addPost({ title: 'Write docs' })}>Add</button>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
)
}
export function App() {
return (
<Provider store={store}>
<Posts />
</Provider>
)
}
```
## Core Patterns
### Keep one API slice per base URL and extend it
```ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
endpoints: () => ({}),
})
export const postsApi = api.injectEndpoints({
endpoints: (build) => ({
getPosts: build.query<{ id: string; title: string }[], void>({
query: () => 'posts',
}),
}),
})
```
Split files with `injectEndpoints`, not by making multiple `createApi` roots for the same backend.
### Use tags for cache invalidation
```ts
type Post = { id: string; title: string }
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
tagTypes: ['Post'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => 'posts',
providesTags: (result) =>
result
? [...result.map(({ id }) => ({ type: 'Post' as const, id })), 'Post']
: ['Post'],
}),
updatePost: build.mutation<Post, Pick<Post, 'id' | 'title'>>({
query: ({ id, title }) => ({
url: `posts/${id}`,
method: 'PATCH',
body: { title },
}),
invalidatesTags: (_result, _error, { id }) => [{ type: 'Post', id }],
}),
}),
})
```
Treat tags as the normal invalidation path before reaching for manual cache patching.
### Do optimistic updates in endpoint lifecycles
```ts
type Post = { id: string; title: string }
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
tagTypes: ['Post'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => 'posts',
providesTags: ['Post'],
}),
updatePostTitle: build.mutation<Post, Pick<Post, 'id' | 'title'>>({
query: ({ id, title }) => ({
url: `posts/${id}`,
method: 'PATCH',
body: { title },
}),
async onQueryStarted({ id, title }, { dispatch, queryFulfilled }) {
const patch = dispatch(
api.util.updateQueryData('getPosts', undefined, (draft) => {
const post = draft.find((item) => item.id === id)
if (post) {
post.title = title
}
}),
)
try {
await queryFulfilled
} catch {
patch.undo()
}
},
}),
}),
})
```
Keep optimistic and pessimistic cache updates inside endpoint lifecycle handlers so they stay coupled to the request.
## Common Mistakes
### CRITICAL Creating multiple API slices for one backend
Wrong:
```ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
type User = { id: string; name: string }
const baseQuery = fetchBaseQuery({ baseUrl: '/api/' })
const postsApi = createApi({
reducerPath: 'api',
baseQuery,
endpoints: () => ({}),
})
const usersApi = createApi({
reducerPath: 'api',
baseQuery,
endpoints: () => ({}),
})
```
Correct:
```ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
type User = { id: string; name: string }
const baseQuery = fetchBaseQuery({ baseUrl: '/api/' })
const api = createApi({
reducerPath: 'api',
baseQuery,
endpoints: () => ({}),
})
const usersApi = api.injectEndpoints({
endpoints: (build) => ({
getUsers: build.query<User[], void>({ query: () => 'users' }),
}),
})
```
One API slice per base URL preserves invalidation behavior and avoids duplicated middleware work.
Source: reduxjs/redux-toolkit:docs/rtk-query/api/createApi.mdx
### HIGH Forgetting `api.reducer` or `api.middleware`
Wrong:
```ts
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({
reducer: {},
})
```
Correct:
```ts
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({
reducer: {
[api.reducerPath]: api.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware),
})
```
RTK Query hooks need both the reducer and middleware to manage cache state and request lifecycles.
Source: reduxjs/redux-toolkit:docs/tutorials/rtk-query.mdx
### MEDIUM Persisting browser API cache by default
Wrong:
```ts
const storage = window.localStorage
const persistConfig = {
key: 'root',
storage,
}
```
Correct:
```ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
endpoints: () => ({}),
})
```
Persisting RTK Query cache in browsers often keeps stale data around longer than users expect; treat persistence as a special case, not the default.
Source: reduxjs/redux-toolkit:docs/rtk-query/usage/persistence-and-rehydration.mdx
### HIGH Patching cache from components
Wrong:
```tsx
import { useEffect } from 'react'
import { useAppDispatch } from '../../app/hooks'
const dispatch = useAppDispatch()
useEffect(() => {
dispatch(api.util.updateQueryData('getPosts', undefined, (draft) => {
draft.push({ id: 'p3', title: 'Patched from component' })
}))
}, [dispatch])
```
Correct:
```ts
updatePostTitle: build.mutation<Post, Pick<Post, 'id' | 'title'>>({
query: ({ id, title }) => ({
url: `posts/${id}`,
method: 'PATCH',
body: { title },
}),
async onQueryStarted({ id, title }, { dispatch, queryFulfilled }) {
const patch = dispatch(
api.util.updateQueryData('getPosts', undefined, (draft) => {
const post = draft.find((item) => item.id === id)
if (post) {
post.title = title
}
}),
)
try {
await queryFulfilled
} catch {
patch.undo()
}
},
})
```
Component-level cache patches drift away from the mutation lifecycle that should own them.
Source: reduxjs/redux-toolkit:docs/rtk-query/usage/manual-cache-updates.mdx
### HIGH Expecting invalidation to refetch unsubscribed queries
Wrong:
```ts
import { api } from './api'
import { store } from './store'
const subscription = store.dispatch(api.endpoints.getPosts.initiate())
subscription.unsubscribe()
store.dispatch(api.util.invalidateTags(['Post']))
```
Correct:
```ts
import { api } from './api'
import { store } from './store'
store.dispatch(api.endpoints.getPosts.initiate())
store.dispatch(api.util.invalidateTags(['Post']))
```
Invalidation only refetches actively subscribed queries; if no component is using that cache entry, RTK Query drops it and fetches again next time it is needed.
Source: reduxjs/redux-toolkit:docs/rtk-query/usage/automated-refetching.mdx
## References
- [Endpoint lifecycle details and cache tradeoffs](references/endpoint-lifecycle.md)

View file

@ -0,0 +1,36 @@
# Endpoint Lifecycle
## Invalidation rule
When a mutation invalidates tags:
- active subscribers refetch
- inactive cache entries are removed
- removed entries fetch again only when something subscribes later
That behavior is deliberate; invalidation is not a background "refresh everything" switch.
## Document cache tradeoff
RTK Query is a document cache, not a normalized entity graph cache.
Use RTK Query by default when:
- the data comes from request/response APIs
- document caching is acceptable
- tag invalidation and endpoint lifecycles solve the problem
Reach for a different tool when:
- the real requirement is a normalized graph cache
- the stack already has a domain-specific normalized client that fits better
If normalized caching is mandatory and no better library is already in the stack, a slice plus thunk flow may be the fallback.
## Useful endpoint options
- `providesTags`: tell RTK Query what cache entries this query represents
- `invalidatesTags`: tell RTK Query what a mutation dirties
- `onQueryStarted`: optimistic and pessimistic updates tied to a request
- `onCacheEntryAdded`: long-lived subscriptions such as streaming data
- `keepUnusedDataFor`: how long inactive cache entries stay around

View file

@ -0,0 +1,364 @@
---
name: model-redux-state/build-slices-and-selectors
description: >
Use this when authoring or refactoring slices with createSlice, selectors,
create.asyncThunk, entity adapters, or lazy reducer injection. Covers
Immer-backed mutation syntax, slice selectors, getSelectors, injectInto,
withLazyLoadedSlices, and current RTK 2 slice patterns.
type: core
library: "@reduxjs/toolkit"
library_version: "2.11.2"
requires:
- model-redux-state/design-state-ownership
sources:
- "reduxjs/redux-toolkit:docs/api/createSlice.mdx"
- "reduxjs/redux-toolkit:docs/api/combineSlices.mdx"
- "reduxjs/redux-toolkit:docs/api/createEntityAdapter.mdx"
- "reduxjs/redux-toolkit:docs/usage/immer-reducers.md"
- "reduxjs/redux-toolkit:docs/usage/migrating-rtk-2.md"
- "reduxjs/redux:docs/style-guide/style-guide.md"
---
# Build Slices And Selectors
## Setup
```ts
// file: src/app/createAppSlice.ts
import { asyncThunkCreator, buildCreateSlice } from '@reduxjs/toolkit'
export const createAppSlice = buildCreateSlice({
creators: { asyncThunk: asyncThunkCreator },
})
// file: src/features/posts/postsSlice.ts
import { createSelector } from '@reduxjs/toolkit'
import { createAppSlice } from '../../app/createAppSlice'
type PostsState = {
items: { id: string; title: string; published: boolean }[]
status: 'idle' | 'pending' | 'succeeded' | 'failed'
}
const initialState: PostsState = {
items: [],
status: 'idle',
}
export const postsSlice = createAppSlice({
name: 'posts',
initialState,
reducers: (create) => ({
postAdded: create.reducer<{ id: string; title: string }>((state, action) => {
state.items.push({ ...action.payload, published: false })
}),
fetchPosts: create.asyncThunk(
async () => {
const response = await fetch('/api/posts')
return (await response.json()) as { id: string; title: string; published: boolean }[]
},
{
pending: (state) => {
state.status = 'pending'
},
fulfilled: (state, action) => {
state.status = 'succeeded'
state.items = action.payload
},
rejected: (state) => {
state.status = 'failed'
},
},
),
}),
selectors: {
selectPosts: (state) => state.items,
selectPublishedPosts: createSelector(
[(state: PostsState) => state.items],
(items) => items.filter((post) => post.published),
),
},
})
export const { postAdded, fetchPosts } = postsSlice.actions
export const { selectPosts, selectPublishedPosts } = postsSlice.selectors
```
## Core Patterns
### Use mutating logic inside slice reducers
```ts
import { createSlice } from '@reduxjs/toolkit'
const todosSlice = createSlice({
name: 'todos',
initialState: [] as { id: string; text: string; done: boolean }[],
reducers: {
todoAdded(state, action: { payload: { id: string; text: string } }) {
state.push({ ...action.payload, done: false })
},
todoToggled(state, action: { payload: { id: string } }) {
const todo = state.find((item) => item.id === action.payload.id)
if (todo) {
todo.done = !todo.done
}
},
},
})
```
Immer is the default inside `createSlice`; write the reducer logic directly instead of copying arrays and objects by hand.
### Define selectors in the slice when they belong to the slice
```ts
import { createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment(state) {
state.value += 1
},
},
selectors: {
selectValue: (state) => state.value,
selectIsPositive: (state) => state.value > 0,
},
})
const { selectValue, selectIsPositive } = counterSlice.selectors
```
Slice selectors keep state-location knowledge next to the slice.
### Use `create.asyncThunk` when the async lifecycle belongs to the slice
```ts
import { asyncThunkCreator, buildCreateSlice } from '@reduxjs/toolkit'
const createAppSlice = buildCreateSlice({
creators: { asyncThunk: asyncThunkCreator },
})
const usersSlice = createAppSlice({
name: 'users',
initialState: { items: [] as { id: string; name: string }[], status: 'idle' as 'idle' | 'pending' | 'failed' },
reducers: (create) => ({
fetchUsers: create.asyncThunk(
async () => {
const response = await fetch('/api/users')
return (await response.json()) as { id: string; name: string }[]
},
{
pending: (state) => {
state.status = 'pending'
},
fulfilled: (state, action) => {
state.status = 'idle'
state.items = action.payload
},
rejected: (state) => {
state.status = 'failed'
},
},
),
}),
})
```
Use this when the async lifecycle handlers naturally live with the slice; otherwise regular `createAsyncThunk` is still fine.
### Use entity adapters and lazy injection for scalable slices
```ts
import {
combineSlices,
createEntityAdapter,
createSlice,
} from '@reduxjs/toolkit'
type Book = { bookId: string; title: string }
const booksAdapter = createEntityAdapter<Book>({
selectId: (book) => book.bookId,
})
const booksSlice = createSlice({
name: 'books',
initialState: booksAdapter.getInitialState(),
reducers: {
booksReceived: booksAdapter.setAll,
},
})
export interface LazyLoadedSlices {}
export const rootReducer =
combineSlices().withLazyLoadedSlices<LazyLoadedSlices>()
declare module './rootReducer' {
export interface LazyLoadedSlices {}
}
const injectedBooksSlice = booksSlice.injectInto(rootReducer)
const selectors = booksAdapter.getSelectors(
(state: ReturnType<typeof rootReducer.selector.original>) =>
injectedBooksSlice.selectSlice(state),
)
```
Entity adapters standardize normalized collections, and `injectInto` lets a slice stay aware of its injected location.
## Common Mistakes
### CRITICAL Using mutating logic outside slice reducers
Wrong:
```ts
type Todo = { id: string; text: string }
export function addTodo(todos: Todo[], todo: Todo) {
todos.push(todo)
return todos
}
```
Correct:
```ts
type Todo = { id: string; text: string }
const todosSlice = createSlice({
name: 'todos',
initialState: [] as Todo[],
reducers: {
todoAdded(state, action: { payload: Todo }) {
state.push(action.payload)
},
},
})
```
Mutation syntax is only safe inside Immer-backed reducer contexts such as `createSlice` and `createReducer`.
Source: reduxjs/redux-toolkit:docs/usage/immer-reducers.md
### HIGH Writing hand-written switch reducers as the default
Wrong:
```ts
export default function todosReducer(state = initialState, action: { type: string; payload?: Todo }) {
switch (action.type) {
case 'todos/todoAdded':
return state.concat(action.payload as Todo)
default:
return state
}
}
```
Correct:
```ts
const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {
todoAdded(state, action: { payload: Todo }) {
state.push(action.payload)
},
},
})
```
Hand-written reducers are an escape hatch for proven bottlenecks, not the normal thing an agent should generate in RTK code.
Source: reduxjs/redux-toolkit:docs/usage/migrating-to-modern-redux.mdx
### HIGH Writing RTK 1.x object syntax for `extraReducers`
Wrong:
```ts
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
const initialState = { items: [] as { id: string; title: string }[] }
const fetchPosts = createAsyncThunk('posts/fetch', async () => {
const response = await fetch('/api/posts')
return (await response.json()) as { id: string; title: string }[]
})
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {},
extraReducers: {
[fetchPosts.fulfilled.type]: (state, action) => {
state.items = action.payload
},
},
})
```
Correct:
```ts
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
const initialState = { items: [] as { id: string; title: string }[] }
const fetchPosts = createAsyncThunk('posts/fetch', async () => {
const response = await fetch('/api/posts')
return (await response.json()) as { id: string; title: string }[]
})
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchPosts.fulfilled, (state, action) => {
state.items = action.payload
})
},
})
```
RTK 2 removed the object form; agents trained on RTK 1.x still generate it.
Source: reduxjs/redux-toolkit:docs/usage/migrating-rtk-2.md
### HIGH Assuming `entity.id` exists for every collection
Wrong:
```ts
type Book = { bookId: string; title: string }
const booksAdapter = createEntityAdapter<Book>()
```
Correct:
```ts
type Book = { bookId: string; title: string }
const booksAdapter = createEntityAdapter<Book>({
selectId: (book) => book.bookId,
})
```
Adapters default to `entity.id`; collections keyed by another field must provide `selectId`.
Source: reduxjs/redux-toolkit:docs/api/createEntityAdapter.mdx
## References
- [Slice selectors, async creators, and lazy injection details](references/slice-patterns.md)

View file

@ -0,0 +1,59 @@
# Slice Patterns
## `getSelectors` for alternate mounting points
```ts
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {},
selectors: {
selectValue: (state) => state.value,
},
})
export const { selectValue } = counterSlice.getSelectors(
(state: { customCounter: { value: number } }) => state.customCounter,
)
```
Use `getSelectors` when the slice is not mounted at its default `reducerPath`.
## `withLazyLoadedSlices` and `injectInto`
```ts
import { combineSlices, createSlice, type WithSlice } from '@reduxjs/toolkit'
const staticSlice = createSlice({
name: 'static',
initialState: { ready: true },
reducers: {},
})
export interface LazyLoadedSlices {}
export const rootReducer =
combineSlices(staticSlice).withLazyLoadedSlices<LazyLoadedSlices>()
const lazySlice = createSlice({
name: 'lazy',
initialState: { value: 0 },
reducers: {
increment(state) {
state.value += 1
},
},
})
declare module '../rootReducer' {
export interface LazyLoadedSlices extends WithSlice<typeof lazySlice> {}
}
export const injectedLazySlice = lazySlice.injectInto(rootReducer)
```
This keeps RootState types aware of reducers that will be injected later.
## Selector caveat
If a selector depends on caller-specific arguments and must be memoized per caller, prefer a selector factory outside `createSlice.selectors`. `createSlice.selectors` gives you one selector instance, not a selector factory.

View file

@ -0,0 +1,322 @@
---
name: model-redux-state/design-state-ownership
description: >
Use this when deciding whether data belongs in Redux, component state, router
state, or another external source. Covers state ownership, authority
boundaries, slice sizing, and when to move or split data as the app evolves.
type: core
library: "@reduxjs/toolkit"
library_version: "2.11.2"
sources:
- "reduxjs/redux:docs/style-guide/style-guide.md"
- "reduxjs/redux:docs/tutorials/essentials/part-2-app-structure.md"
- "reduxjs/redux:docs/tutorials/essentials/part-4-using-data.md"
---
# Design State Ownership
## Setup
```tsx
import { useState } from 'react'
import { createSlice } from '@reduxjs/toolkit'
import { useAppDispatch } from '../../app/hooks'
const postsSlice = createSlice({
name: 'posts',
initialState: [] as { id: string; title: string; content: string }[],
reducers: {
postAdded(
state,
action: { payload: { id: string; title: string; content: string } },
) {
state.push(action.payload)
},
},
})
const { postAdded } = postsSlice.actions
export function AddPostForm() {
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const dispatch = useAppDispatch()
return (
<form
onSubmit={(event) => {
event.preventDefault()
dispatch(postAdded({ id: 'p1', title, content }))
}}
>
<input value={title} onChange={(event) => setTitle(event.target.value)} />
<textarea
value={content}
onChange={(event) => setContent(event.target.value)}
/>
<button type="submit">Save</button>
</form>
)
}
```
## Core Patterns
### Keep editable form state local until the user commits it
```tsx
import { useState } from 'react'
import { useAppDispatch } from '../../app/hooks'
import { profileSaved } from './profileSlice'
export function ProfileForm() {
const [displayName, setDisplayName] = useState('Lenz')
const dispatch = useAppDispatch()
return (
<form
onSubmit={(event) => {
event.preventDefault()
dispatch(profileSaved({ displayName }))
}}
>
<input
value={displayName}
onChange={(event) => setDisplayName(event.target.value)}
/>
<button type="submit">Save</button>
</form>
)
}
```
Prefer Redux for shared, durable app state, not every keystroke.
### Keep URL state with the router and combine it at the edge
```tsx
import { createSelector } from '@reduxjs/toolkit'
import { useSearchParams } from 'react-router-dom'
import { useAppSelector } from '../../app/hooks'
type RootState = {
posts: {
items: { id: string; title: string; published: boolean }[]
}
}
const selectPosts = (state: RootState) => state.posts.items
const selectVisiblePosts = createSelector(
[selectPosts, (_state: RootState, filter: string) => filter],
(posts, filter) =>
filter === 'published'
? posts.filter((post) => post.published)
: posts,
)
export function PostsList() {
const [searchParams] = useSearchParams()
const filter = searchParams.get('filter') ?? 'all'
const posts = useAppSelector((state) => selectVisiblePosts(state, filter))
return <div>{posts.length}</div>
}
```
If the router already owns a piece of state, pass it into selectors or combine it in the component instead of syncing it into Redux.
### Re-size slices when access patterns change
```ts
import { combineReducers, createSlice } from '@reduxjs/toolkit'
const authSlice = createSlice({
name: 'auth',
initialState: { userId: null as string | null },
reducers: {},
})
const postsSlice = createSlice({
name: 'posts',
initialState: { items: [] as { id: string; title: string }[] },
reducers: {},
})
export const rootReducer = combineReducers({
auth: authSlice.reducer,
posts: postsSlice.reducer,
})
```
Revisit slice size over time; unrelated data should split apart, and data constantly stitched together in every component may belong closer together.
## Common Mistakes
### MEDIUM Putting form editing state in Redux
Wrong:
```tsx
import { useAppSelector } from '../../app/hooks'
const selectDraftTitle = (state: { draft: { title: string } }) => state.draft.title
const title = useAppSelector(selectDraftTitle)
```
Correct:
```tsx
const [title, setTitle] = useState('')
<input value={title} onChange={(event) => setTitle(event.target.value)} />
```
Per-keystroke dispatching adds global complexity for data that usually lives in one component tree.
Source: reduxjs/redux:docs/style-guide/style-guide.md
### HIGH Synchronizing router or URL state into Redux
Wrong:
```tsx
import { useEffect } from 'react'
import { useSearchParams } from 'react-router-dom'
import { useAppDispatch } from '../../app/hooks'
function PostsPage() {
const [searchParams] = useSearchParams()
const dispatch = useAppDispatch()
useEffect(() => {
dispatch(filterChanged(searchParams.get('filter') ?? 'all'))
}, [dispatch, searchParams])
return null
}
```
Correct:
```tsx
const filter = searchParams.get('filter') ?? 'all'
const posts = useAppSelector((state) => selectVisiblePosts(state, filter))
```
URL state already has an authoritative owner; duplicating it into Redux creates two sources of truth.
Source: maintainer interview
### HIGH Naming state after components
Wrong:
```ts
import { combineReducers } from '@reduxjs/toolkit'
const loginReducer = (state = { open: false }) => state
const postsReducer = (state = [] as { id: string; title: string }[]) => state
const rootReducer = combineReducers({
loginScreen: loginReducer,
postsList: postsReducer,
})
```
Correct:
```ts
import { combineReducers } from '@reduxjs/toolkit'
const authReducer = (state = { userId: null as string | null }) => state
const postsReducer = (state = [] as { id: string; title: string }[]) => state
const rootReducer = combineReducers({
auth: authReducer,
posts: postsReducer,
})
```
Store keys should describe data or domain concepts, not the current component tree.
Source: reduxjs/redux:docs/style-guide/style-guide.md
### MEDIUM Letting slice boundaries fossilize
Wrong:
```ts
import { createSlice } from '@reduxjs/toolkit'
type Post = { id: string; title: string }
type AppNotification = { id: string; message: string }
const appSlice = createSlice({
name: 'app',
initialState: {
auth: { userId: null as string | null },
posts: [] as Post[],
notifications: [] as AppNotification[],
},
reducers: {},
})
```
Correct:
```ts
import { createSlice } from '@reduxjs/toolkit'
type Post = { id: string; title: string }
const authSlice = createSlice({
name: 'auth',
initialState: { userId: null as string | null },
reducers: {},
})
const postsSlice = createSlice({
name: 'posts',
initialState: [] as Post[],
reducers: {},
})
```
When unrelated data is welded together, every change point gets noisier; split or merge slices as actual access patterns demand.
Source: maintainer interview
### HIGH Blindly spreading payloads into state
Wrong:
```ts
const state = { id: '1', name: 'Lenz' }
const action = { payload: { id: '2', name: 'Mark', ignored: true } }
userLoggedIn(state, action) {
return { ...state, ...action.payload }
}
```
Correct:
```ts
const state = { id: '1', name: 'Lenz' }
const action = { payload: { id: '2', name: 'Mark', ignored: true } }
userLoggedIn(state, action) {
state.id = action.payload.id
state.name = action.payload.name
}
```
Reducers should own the slice shape instead of treating payloads as trusted state patches.
Source: reduxjs/redux:docs/style-guide/style-guide.md
## References
- [State ownership heuristics](references/state-ownership.md)

View file

@ -0,0 +1,28 @@
# State Ownership Heuristics
## Choose the owner, then the tool
| Kind of state | Default owner | Typical tool |
| --- | --- | --- |
| Editable form fields | Component | `useState` |
| Shared mutable app data | Redux | Slice state |
| Server cache | RTK Query | `createApi` |
| URL, pathname, search params | Router | Router APIs plus selector inputs |
| Browser-only authority like `localStorage` | External source | Read at boundaries, then dispatch events |
## Good reasons to move data into Redux
- Multiple distant parts of the UI need the same mutable data.
- You need time-travel debugging or a stable action history.
- The reducer should own transitions because they mix old store state with new inputs.
## Reasons to keep data out of Redux
- Another system already owns it, such as the router.
- It only matters during editing inside one component tree.
- It is server cache and RTK Query fits the use case better.
## Re-evaluate slice size
- If data is constantly stitched together outside reducers, it may belong closer together.
- If unrelated updates keep touching the same slice, split the slice by domain ownership.

View file

@ -0,0 +1,271 @@
---
name: orchestrate-side-effects/handle-side-effects
description: >
Use this when choosing between RTK Query, createAsyncThunk, handwritten
thunks, and createListenerMiddleware. Covers imperative versus reactive
workflows, listener middleware setup, and keeping side effects out of
reducers and UI components.
type: core
library: "@reduxjs/toolkit"
library_version: "2.11.2"
requires:
- build-modern-redux-apps/redux-dataflow
sources:
- "reduxjs/redux-toolkit:docs/api/createAsyncThunk.mdx"
- "reduxjs/redux-toolkit:docs/api/createListenerMiddleware.mdx"
- "reduxjs/redux:docs/style-guide/style-guide.md"
- "reduxjs/redux:docs/tutorials/essentials/part-5-async-logic.md"
---
# Handle Side Effects
## Setup
```ts
import {
configureStore,
createListenerMiddleware,
createSlice,
} from '@reduxjs/toolkit'
const docsSlice = createSlice({
name: 'docs',
initialState: { status: 'idle' as 'idle' | 'saved' },
reducers: {
saveStarted(state) {
state.status = 'idle'
},
saveFinished(state) {
state.status = 'saved'
},
},
})
const listenerMiddleware = createListenerMiddleware()
export const store = configureStore({
reducer: {
docs: docsSlice.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().prepend(listenerMiddleware.middleware),
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export const startAppListening =
listenerMiddleware.startListening.withTypes<RootState, AppDispatch>()
```
## Core Patterns
### Use RTK Query for server cache by default
```ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
type Post = { id: string; title: string }
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
tagTypes: ['Post'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => 'posts',
providesTags: ['Post'],
}),
}),
})
```
If the problem is server data that should be cached and re-used, start with RTK Query instead of a thunk.
### Use `createAsyncThunk` for imperative workflows
```ts
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
type Draft = { title: string }
export const draftSaved = createAsyncThunk(
'drafts/save',
async (draft: Draft) => {
const response = await fetch('/api/drafts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(draft),
})
return (await response.json()) as { id: string; title: string }
},
)
const draftsSlice = createSlice({
name: 'drafts',
initialState: { status: 'idle' as 'idle' | 'pending' | 'failed' },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(draftSaved.pending, (state) => {
state.status = 'pending'
})
.addCase(draftSaved.fulfilled, (state) => {
state.status = 'idle'
})
.addCase(draftSaved.rejected, (state) => {
state.status = 'failed'
})
},
})
```
Use a thunk when you need one imperative async workflow with `dispatch` and `getState`.
### Use listener middleware for reactive workflows
```ts
import { createListenerMiddleware, createSlice } from '@reduxjs/toolkit'
const docsSlice = createSlice({
name: 'docs',
initialState: { status: 'idle' as 'idle' | 'saved' },
reducers: {
saveFinished(state) {
state.status = 'saved'
},
},
})
const notificationsSlice = createSlice({
name: 'notifications',
initialState: [] as string[],
reducers: {
notificationQueued(state, action: { payload: string }) {
state.push(action.payload)
},
},
})
const listenerMiddleware = createListenerMiddleware()
listenerMiddleware.startListening({
actionCreator: docsSlice.actions.saveFinished,
effect: async (_action, listenerApi) => {
listenerApi.dispatch(
notificationsSlice.actions.notificationQueued('Document saved'),
)
},
})
```
Listeners fit workflows that react to future actions or state changes over time instead of driving one imperative request from a single callsite.
## Common Mistakes
### CRITICAL Running side effects inside reducers
Wrong:
```ts
const todosSlice = createSlice({
name: 'todos',
initialState: [] as { id: string }[],
reducers: {
todoSaved(state, action: { payload: { id: string } }) {
fetch('/api/todos', { method: 'POST' })
state.push(action.payload)
},
},
})
```
Correct:
```ts
import { createAsyncThunk } from '@reduxjs/toolkit'
const todoSaved = createAsyncThunk('todos/save', async (todo: { id: string }) => {
await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(todo),
})
return todo
})
```
Reducers must stay pure even when Immer is available.
Source: reduxjs/redux:docs/style-guide/style-guide.md
### HIGH Using thunks to watch future state changes
Wrong:
```ts
export const waitForSave = () => async (
_dispatch: unknown,
getState: () => { docs: { status: string } },
) => {
while (getState().docs.status !== 'saved') {
await new Promise((resolve) => setTimeout(resolve, 100))
}
}
```
Correct:
```ts
startAppListening({
predicate: (_action, currentState) => currentState.docs.status === 'saved',
effect: async () => {
console.log('Document saved')
},
})
```
Polling inside thunks fights the architecture; listener middleware is the reactive tool.
Source: reduxjs/redux-toolkit:docs/api/createListenerMiddleware.mdx
### HIGH Appending listener middleware after the default checks
Wrong:
```ts
import { configureStore, createListenerMiddleware } from '@reduxjs/toolkit'
const reducer = (state = { ready: true }) => state
const listenerMiddleware = createListenerMiddleware()
const store = configureStore({
reducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(listenerMiddleware.middleware),
})
```
Correct:
```ts
import { configureStore, createListenerMiddleware } from '@reduxjs/toolkit'
const reducer = (state = { ready: true }) => state
const listenerMiddleware = createListenerMiddleware()
const store = configureStore({
reducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().prepend(listenerMiddleware.middleware),
})
```
Listener add and remove actions may carry functions, so the listener middleware needs to run before serializability checks.
Source: reduxjs/redux-toolkit:docs/api/createListenerMiddleware.mdx
## References
- [Listener helpers and decision heuristics](references/listener-workflows.md)

View file

@ -0,0 +1,34 @@
# Listener Workflows
## Decision table
| Need | Reach for |
| --- | --- |
| Cached server data | RTK Query |
| One imperative async workflow with `dispatch` / `getState` | `createAsyncThunk` or a thunk |
| React to later actions or state transitions | `createListenerMiddleware` |
A good app often mixes imperative and reactive workflows. The split is by job, not by ideology.
## Useful listener helpers
- `predicate`: react to any action when a state condition becomes true
- `condition`: wait until a condition becomes true before continuing
- `take`: wait for the next matching action
- `cancelActiveListeners`: cancel older instances of the same workflow
- `fork`: start a child task
## Example: cancel stale work
```ts
startAppListening({
actionCreator: searchRequested,
effect: async (action, listenerApi) => {
listenerApi.cancelActiveListeners()
await listenerApi.delay(250)
listenerApi.dispatch(searchStarted(action.payload))
},
})
```
This is the kind of long-lived reactive behavior that does not fit a thunk well.

View file

@ -0,0 +1,34 @@
import type { Middleware } from 'redux'
import { isActionCreator as isRTKAction } from './createAction'
export interface ActionCreatorInvariantMiddlewareOptions {
/**
* The function to identify whether a value is an action creator.
* The default checks for a function with a static type property and match method.
*/
isActionCreator?: (action: unknown) => action is Function & { type?: unknown }
}
export function getMessage(type?: unknown) {
const splitType = type ? `${type}`.split('/') : []
const actionName = splitType[splitType.length - 1] || 'actionCreator'
return `Detected an action creator with type "${
type || 'unknown'
}" being dispatched.
Make sure you're calling the action creator before dispatching, i.e. \`dispatch(${actionName}())\` instead of \`dispatch(${actionName})\`. This is necessary even if the action has no payload.`
}
export function createActionCreatorInvariantMiddleware(
options: ActionCreatorInvariantMiddlewareOptions = {},
): Middleware {
if (process.env.NODE_ENV === 'production') {
return () => (next) => (action) => next(action)
}
const { isActionCreator = isRTKAction } = options
return () => (next) => (action) => {
if (isActionCreator(action)) {
console.warn(getMessage(action.type))
}
return next(action)
}
}

View file

@ -0,0 +1,146 @@
import type { StoreEnhancer } from 'redux'
export const SHOULD_AUTOBATCH = 'RTK_autoBatch'
export const prepareAutoBatched =
<T>() =>
(payload: T): { payload: T; meta: unknown } => ({
payload,
meta: { [SHOULD_AUTOBATCH]: true },
})
const createQueueWithTimer = (timeout: number) => {
return (notify: () => void) => {
setTimeout(notify, timeout)
}
}
const createRafWithFallbackTimer = (
raf: typeof requestAnimationFrame,
timeout: number,
) => {
return (notify: () => void) => {
let called = false
const callback = () => {
if (called) return
called = true
cancelAnimationFrame(rafId)
clearTimeout(timerId)
notify()
}
const rafId = raf(callback)
const timerId = setTimeout(callback, timeout)
}
}
export type AutoBatchOptions =
| { type: 'tick' }
| { type: 'timer'; timeout: number }
| { type: 'raf' }
| { type: 'callback'; queueNotification: (notify: () => void) => void }
/**
* A Redux store enhancer that watches for "low-priority" actions, and delays
* notifying subscribers until either the queued callback executes or the
* next "standard-priority" action is dispatched.
*
* This allows dispatching multiple "low-priority" actions in a row with only
* a single subscriber notification to the UI after the sequence of actions
* is finished, thus improving UI re-render performance.
*
* Watches for actions with the `action.meta[SHOULD_AUTOBATCH]` attribute.
* This can be added to `action.meta` manually, or by using the
* `prepareAutoBatched` helper.
*
* By default, it will queue a notification for the end of the event loop tick.
* However, you can pass several other options to configure the behavior:
* - `{type: 'tick'}`: queues using `queueMicrotask`
* - `{type: 'timer', timeout: number}`: queues using `setTimeout`
* - `{type: 'raf'}`: queues using `requestAnimationFrame` (default)
* - `{type: 'callback', queueNotification: (notify: () => void) => void}`: lets you provide your own callback
*
*
*/
export const autoBatchEnhancer =
(options: AutoBatchOptions = { type: 'raf' }): StoreEnhancer =>
(next) =>
(...args) => {
const store = next(...args)
let notifying = true
let shouldNotifyAtEndOfTick = false
let notificationQueued = false
const listeners = new Set<() => void>()
const queueCallback =
options.type === 'tick'
? queueMicrotask
: options.type === 'raf'
? // requestAnimationFrame won't exist in SSR environments. Fall back to a vague approximation just to keep from erroring.
typeof window !== 'undefined' && window.requestAnimationFrame
? createRafWithFallbackTimer(window.requestAnimationFrame, 100)
: createQueueWithTimer(10)
: options.type === 'callback'
? options.queueNotification
: createQueueWithTimer(options.timeout)
const notifyListeners = () => {
// We're running at the end of the event loop tick.
// Run the real listener callbacks to actually update the UI.
notificationQueued = false
if (shouldNotifyAtEndOfTick) {
shouldNotifyAtEndOfTick = false
listeners.forEach((l) => l())
}
}
return Object.assign({}, store, {
// Override the base `store.subscribe` method to keep original listeners
// from running if we're delaying notifications
subscribe(listener: () => void) {
// Each wrapped listener will only call the real listener if
// the `notifying` flag is currently active when it's called.
// This lets the base store work as normal, while the actual UI
// update becomes controlled by this enhancer.
const wrappedListener: typeof listener = () => notifying && listener()
const unsubscribe = store.subscribe(wrappedListener)
listeners.add(listener)
return () => {
unsubscribe()
listeners.delete(listener)
}
},
// Override the base `store.dispatch` method so that we can check actions
// for the `shouldAutoBatch` flag and determine if batching is active
dispatch(action: any) {
try {
// If the action does _not_ have the `shouldAutoBatch` flag,
// we resume/continue normal notify-after-each-dispatch behavior
notifying = !action?.meta?.[SHOULD_AUTOBATCH]
// If a `notifyListeners` microtask was queued, you can't cancel it.
// Instead, we set a flag so that it's a no-op when it does run
shouldNotifyAtEndOfTick = !notifying
if (shouldNotifyAtEndOfTick) {
// We've seen at least 1 action with `SHOULD_AUTOBATCH`. Try to queue
// a microtask to notify listeners at the end of the event loop tick.
// Make sure we only enqueue this _once_ per tick.
if (!notificationQueued) {
notificationQueued = true
queueCallback(notifyListeners)
}
}
// Go ahead and process the action as usual, including reducers.
// If normal notification behavior is enabled, the store will notify
// all of its own listeners, and the wrapper callbacks above will
// see `notifying` is true and pass on to the real listener callbacks.
// If we're "batching" behavior, then the wrapped callbacks will
// bail out, causing the base store notification behavior to be no-ops.
return store.dispatch(action)
} finally {
// Assume we're back to normal behavior after each action
notifying = true
}
},
})
}

View file

@ -0,0 +1,487 @@
import type {
PreloadedStateShapeFromReducersMapObject,
Reducer,
StateFromReducersMapObject,
UnknownAction,
} from 'redux'
import { combineReducers } from 'redux'
import { nanoid } from './nanoid'
import type {
Id,
NonUndefined,
Tail,
UnionToIntersection,
WithOptionalProp,
} from './tsHelpers'
import { getOrInsertComputed } from './utils'
type SliceLike<ReducerPath extends string, State, PreloadedState = State> = {
reducerPath: ReducerPath
reducer: Reducer<State, any, PreloadedState>
}
type AnySliceLike = SliceLike<string, any>
type SliceLikeReducerPath<A extends AnySliceLike> =
A extends SliceLike<infer ReducerPath, any> ? ReducerPath : never
type SliceLikeState<A extends AnySliceLike> =
A extends SliceLike<any, infer State, any> ? State : never
type SliceLikePreloadedState<A extends AnySliceLike> =
A extends SliceLike<any, any, infer PreloadedState> ? PreloadedState : never
export type WithSlice<A extends AnySliceLike> = {
[Path in SliceLikeReducerPath<A>]: SliceLikeState<A>
}
export type WithSlicePreloadedState<A extends AnySliceLike> = {
[Path in SliceLikeReducerPath<A>]: SliceLikePreloadedState<A>
}
type ReducerMap = Record<string, Reducer>
type ExistingSliceLike<DeclaredState, PreloadedState> = {
[ReducerPath in keyof DeclaredState]: SliceLike<
ReducerPath & string,
NonUndefined<DeclaredState[ReducerPath]>,
NonUndefined<PreloadedState[ReducerPath & keyof PreloadedState]>
>
}[keyof DeclaredState]
export type InjectConfig = {
/**
* Allow replacing reducer with a different reference. Normally, an error will be thrown if a different reducer instance to the one already injected is used.
*/
overrideExisting?: boolean
}
/**
* A reducer that allows for slices/reducers to be injected after initialisation.
*/
export interface CombinedSliceReducer<
InitialState,
DeclaredState extends InitialState = InitialState,
PreloadedState extends Partial<
Record<keyof PreloadedState, any>
> = Partial<DeclaredState>,
> extends Reducer<DeclaredState, UnknownAction, PreloadedState> {
/**
* Provide a type for slices that will be injected lazily.
*
* One way to do this would be with interface merging:
* ```ts
*
* export interface LazyLoadedSlices {}
*
* export const rootReducer = combineSlices(stringSlice).withLazyLoadedSlices<LazyLoadedSlices>();
*
* // elsewhere
*
* declare module './reducer' {
* export interface LazyLoadedSlices extends WithSlice<typeof booleanSlice> {}
* }
*
* const withBoolean = rootReducer.inject(booleanSlice);
*
* // elsewhere again
*
* declare module './reducer' {
* export interface LazyLoadedSlices {
* customName: CustomState
* }
* }
*
* const withCustom = rootReducer.inject({ reducerPath: "customName", reducer: customSlice.reducer })
* ```
*/
withLazyLoadedSlices<Lazy = {}, LazyPreloaded = Lazy>(): CombinedSliceReducer<
InitialState,
Id<DeclaredState & Partial<Lazy>>,
Id<PreloadedState & Partial<LazyPreloaded>>
>
/**
* Inject a slice.
*
* Accepts an individual slice, RTKQ API instance, or a "slice-like" { reducerPath, reducer } object.
*
* ```ts
* rootReducer.inject(booleanSlice)
* rootReducer.inject(baseApi)
* rootReducer.inject({ reducerPath: 'boolean' as const, reducer: newReducer }, { overrideExisting: true })
* ```
*
*/
inject<Sl extends Id<ExistingSliceLike<DeclaredState, PreloadedState>>>(
slice: Sl,
config?: InjectConfig,
): CombinedSliceReducer<
InitialState,
Id<DeclaredState & WithSlice<Sl>>,
Id<PreloadedState & Partial<WithSlicePreloadedState<Sl>>>
>
/**
* Inject a slice.
*
* Accepts an individual slice, RTKQ API instance, or a "slice-like" { reducerPath, reducer } object.
*
* ```ts
* rootReducer.inject(booleanSlice)
* rootReducer.inject(baseApi)
* rootReducer.inject({ reducerPath: 'boolean' as const, reducer: newReducer }, { overrideExisting: true })
* ```
*
*/
inject<ReducerPath extends string, State, PreloadedState = State>(
slice: SliceLike<
ReducerPath,
State & (ReducerPath extends keyof DeclaredState ? never : State),
PreloadedState &
(ReducerPath extends keyof PreloadedState ? never : PreloadedState)
>,
config?: InjectConfig,
): CombinedSliceReducer<
InitialState,
Id<DeclaredState & WithSlice<SliceLike<ReducerPath, State>>>,
Id<
PreloadedState &
WithSlicePreloadedState<SliceLike<ReducerPath, State, PreloadedState>>
>
>
/**
* Create a selector that guarantees that the slices injected will have a defined value when selector is run.
*
* ```ts
* const selectBooleanWithoutInjection = (state: RootState) => state.boolean;
* // ^? boolean | undefined
*
* const selectBoolean = rootReducer.inject(booleanSlice).selector((state) => {
* // if action hasn't been dispatched since slice was injected, this would usually be undefined
* // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined
* return state.boolean;
* // ^? boolean
* })
* ```
*
* If the reducer is nested inside the root state, a selectState callback can be passed to retrieve the reducer's state.
*
* ```ts
*
* export interface LazyLoadedSlices {};
*
* export const innerReducer = combineSlices(stringSlice).withLazyLoadedSlices<LazyLoadedSlices>();
*
* export const rootReducer = combineSlices({ inner: innerReducer });
*
* export type RootState = ReturnType<typeof rootReducer>;
*
* // elsewhere
*
* declare module "./reducer.ts" {
* export interface LazyLoadedSlices extends WithSlice<typeof booleanSlice> {}
* }
*
* const withBool = innerReducer.inject(booleanSlice);
*
* const selectBoolean = withBool.selector(
* (state) => state.boolean,
* (rootState: RootState) => state.inner
* );
* // now expects to be passed RootState instead of innerReducer state
*
* ```
*
* Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging)
*
* ```ts
* const injectedReducer = rootReducer.inject(booleanSlice);
* const selectBoolean = injectedReducer.selector((state) => {
* console.log(injectedReducer.selector.original(state).boolean) // possibly undefined
* return state.boolean
* })
* ```
*/
selector: {
/**
* Create a selector that guarantees that the slices injected will have a defined value when selector is run.
*
* ```ts
* const selectBooleanWithoutInjection = (state: RootState) => state.boolean;
* // ^? boolean | undefined
*
* const selectBoolean = rootReducer.inject(booleanSlice).selector((state) => {
* // if action hasn't been dispatched since slice was injected, this would usually be undefined
* // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined
* return state.boolean;
* // ^? boolean
* })
* ```
*
* Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging)
*
* ```ts
* const injectedReducer = rootReducer.inject(booleanSlice);
* const selectBoolean = injectedReducer.selector((state) => {
* console.log(injectedReducer.selector.original(state).boolean) // undefined
* return state.boolean
* })
* ```
*/
<Selector extends (state: DeclaredState, ...args: any[]) => unknown>(
selectorFn: Selector,
): (
state: WithOptionalProp<
Parameters<Selector>[0],
Exclude<keyof DeclaredState, keyof InitialState>
>,
...args: Tail<Parameters<Selector>>
) => ReturnType<Selector>
/**
* Create a selector that guarantees that the slices injected will have a defined value when selector is run.
*
* ```ts
* const selectBooleanWithoutInjection = (state: RootState) => state.boolean;
* // ^? boolean | undefined
*
* const selectBoolean = rootReducer.inject(booleanSlice).selector((state) => {
* // if action hasn't been dispatched since slice was injected, this would usually be undefined
* // however selector() uses a Proxy around the first parameter to ensure that it evaluates to the initial state instead, if undefined
* return state.boolean;
* // ^? boolean
* })
* ```
*
* If the reducer is nested inside the root state, a selectState callback can be passed to retrieve the reducer's state.
*
* ```ts
*
* interface LazyLoadedSlices {};
*
* const innerReducer = combineSlices(stringSlice).withLazyLoadedSlices<LazyLoadedSlices>();
*
* const rootReducer = combineSlices({ inner: innerReducer });
*
* type RootState = ReturnType<typeof rootReducer>;
*
* // elsewhere
*
* declare module "./reducer.ts" {
* interface LazyLoadedSlices extends WithSlice<typeof booleanSlice> {}
* }
*
* const withBool = innerReducer.inject(booleanSlice);
*
* const selectBoolean = withBool.selector(
* (state) => state.boolean,
* (rootState: RootState) => state.inner
* );
* // now expects to be passed RootState instead of innerReducer state
*
* ```
*
* Value passed to selectorFn will be a Proxy - use selector.original(proxy) to get original state value (useful for debugging)
*
* ```ts
* const injectedReducer = rootReducer.inject(booleanSlice);
* const selectBoolean = injectedReducer.selector((state) => {
* console.log(injectedReducer.selector.original(state).boolean) // possibly undefined
* return state.boolean
* })
* ```
*/
<
Selector extends (state: DeclaredState, ...args: any[]) => unknown,
RootState,
>(
selectorFn: Selector,
selectState: (
rootState: RootState,
...args: Tail<Parameters<Selector>>
) => WithOptionalProp<
Parameters<Selector>[0],
Exclude<keyof DeclaredState, keyof InitialState>
>,
): (
state: RootState,
...args: Tail<Parameters<Selector>>
) => ReturnType<Selector>
/**
* Returns the unproxied state. Useful for debugging.
* @param state state Proxy, that ensures injected reducers have value
* @returns original, unproxied state
* @throws if value passed is not a state Proxy
*/
original: (state: DeclaredState) => InitialState & Partial<DeclaredState>
}
}
type InitialState<Slices extends Array<AnySliceLike | ReducerMap>> =
UnionToIntersection<
Slices[number] extends infer Slice
? Slice extends AnySliceLike
? WithSlice<Slice>
: StateFromReducersMapObject<Slice>
: never
>
type InitialPreloadedState<Slices extends Array<AnySliceLike | ReducerMap>> =
UnionToIntersection<
Slices[number] extends infer Slice
? Slice extends AnySliceLike
? WithSlicePreloadedState<Slice>
: PreloadedStateShapeFromReducersMapObject<Slice>
: never
>
const isSliceLike = (
maybeSliceLike: AnySliceLike | ReducerMap,
): maybeSliceLike is AnySliceLike =>
'reducerPath' in maybeSliceLike &&
typeof maybeSliceLike.reducerPath === 'string'
const getReducers = (slices: Array<AnySliceLike | ReducerMap>) =>
slices.flatMap<[string, Reducer]>((sliceOrMap) =>
isSliceLike(sliceOrMap)
? [[sliceOrMap.reducerPath, sliceOrMap.reducer]]
: Object.entries(sliceOrMap),
)
const ORIGINAL_STATE = Symbol.for('rtk-state-proxy-original')
const isStateProxy = (value: any) => !!value && !!value[ORIGINAL_STATE]
const stateProxyMap = new WeakMap<object, object>()
const createStateProxy = <State extends object>(
state: State,
reducerMap: Partial<Record<PropertyKey, Reducer>>,
initialStateCache: Record<PropertyKey, unknown>,
) =>
getOrInsertComputed(
stateProxyMap,
state,
() =>
new Proxy(state, {
get: (target, prop, receiver) => {
if (prop === ORIGINAL_STATE) return target
const result = Reflect.get(target, prop, receiver)
if (typeof result === 'undefined') {
const cached = initialStateCache[prop]
if (typeof cached !== 'undefined') return cached
const reducer = reducerMap[prop]
if (reducer) {
// ensure action type is random, to prevent reducer treating it differently
const reducerResult = reducer(undefined, { type: nanoid() })
if (typeof reducerResult === 'undefined') {
throw new Error(
`The slice reducer for key "${prop.toString()}" returned undefined when called for selector(). ` +
`If the state passed to the reducer is undefined, you must ` +
`explicitly return the initial state. The initial state may ` +
`not be undefined. If you don't want to set a value for this reducer, ` +
`you can use null instead of undefined.`,
)
}
initialStateCache[prop] = reducerResult
return reducerResult
}
}
return result
},
}),
) as State
const original = (state: any) => {
if (!isStateProxy(state)) {
throw new Error('original must be used on state Proxy')
}
return state[ORIGINAL_STATE]
}
const emptyObject = {}
const noopReducer: Reducer<Record<string, any>> = (state = emptyObject) => state
export function combineSlices<Slices extends Array<AnySliceLike | ReducerMap>>(
...slices: Slices
): CombinedSliceReducer<
Id<InitialState<Slices>>,
Id<InitialState<Slices>>,
Partial<Id<InitialPreloadedState<Slices>>>
> {
const reducerMap = Object.fromEntries(getReducers(slices))
const getReducer = () =>
Object.keys(reducerMap).length ? combineReducers(reducerMap) : noopReducer
let reducer = getReducer()
function combinedReducer(
state: Record<string, unknown>,
action: UnknownAction,
) {
return reducer(state, action)
}
combinedReducer.withLazyLoadedSlices = () => combinedReducer
const initialStateCache: Record<PropertyKey, unknown> = {}
const inject = (
slice: AnySliceLike,
config: InjectConfig = {},
): typeof combinedReducer => {
const { reducerPath, reducer: reducerToInject } = slice
const currentReducer = reducerMap[reducerPath]
if (
!config.overrideExisting &&
currentReducer &&
currentReducer !== reducerToInject
) {
if (
typeof process !== 'undefined' &&
process.env.NODE_ENV === 'development'
) {
console.error(
`called \`inject\` to override already-existing reducer ${reducerPath} without specifying \`overrideExisting: true\``,
)
}
return combinedReducer
}
if (config.overrideExisting && currentReducer !== reducerToInject) {
delete initialStateCache[reducerPath]
}
reducerMap[reducerPath] = reducerToInject
reducer = getReducer()
return combinedReducer
}
const selector = Object.assign(
function makeSelector<State extends object, RootState, Args extends any[]>(
selectorFn: (state: State, ...args: Args) => any,
selectState?: (rootState: RootState, ...args: Args) => State,
) {
return function selector(state: State, ...args: Args) {
return selectorFn(
createStateProxy(
selectState ? selectState(state as any, ...args) : state,
reducerMap,
initialStateCache,
),
...args,
)
}
},
{ original },
)
return Object.assign(combinedReducer, { inject, selector }) as any
}

View file

@ -0,0 +1,248 @@
import type {
Reducer,
ReducersMapObject,
Middleware,
Action,
StoreEnhancer,
Store,
UnknownAction,
} from 'redux'
import {
applyMiddleware,
createStore,
compose,
combineReducers,
isPlainObject,
} from './reduxImports'
import type { DevToolsEnhancerOptions as DevToolsOptions } from './devtoolsExtension'
import { composeWithDevTools } from './devtoolsExtension'
import type {
ThunkMiddlewareFor,
GetDefaultMiddleware,
} from './getDefaultMiddleware'
import { buildGetDefaultMiddleware } from './getDefaultMiddleware'
import type {
ExtractDispatchExtensions,
ExtractStoreExtensions,
ExtractStateExtensions,
UnknownIfNonSpecific,
} from './tsHelpers'
import type { Tuple } from './utils'
import type { GetDefaultEnhancers } from './getDefaultEnhancers'
import { buildGetDefaultEnhancers } from './getDefaultEnhancers'
/**
* Options for `configureStore()`.
*
* @public
*/
export interface ConfigureStoreOptions<
S = any,
A extends Action = UnknownAction,
M extends Tuple<Middlewares<S>> = Tuple<Middlewares<S>>,
E extends Tuple<Enhancers> = Tuple<Enhancers>,
P = S,
> {
/**
* A single reducer function that will be used as the root reducer, or an
* object of slice reducers that will be passed to `combineReducers()`.
*/
reducer: Reducer<S, A, P> | ReducersMapObject<S, A, P>
/**
* An array of Redux middleware to install, or a callback receiving `getDefaultMiddleware` and returning a Tuple of middleware.
* If not supplied, defaults to the set of middleware returned by `getDefaultMiddleware()`.
*
* @example `middleware: (gDM) => gDM().concat(logger, apiMiddleware, yourCustomMiddleware)`
* @see https://redux-toolkit.js.org/api/getDefaultMiddleware#intended-usage
*/
middleware?: (getDefaultMiddleware: GetDefaultMiddleware<S>) => M
/**
* Whether to enable Redux DevTools integration. Defaults to `true`.
*
* Additional configuration can be done by passing Redux DevTools options
*/
devTools?: boolean | DevToolsOptions
/**
* Whether to check for duplicate middleware instances. Defaults to `true`.
*/
duplicateMiddlewareCheck?: boolean
/**
* The initial state, same as Redux's createStore.
* You may optionally specify it to hydrate the state
* from the server in universal apps, or to restore a previously serialized
* user session. If you use `combineReducers()` to produce the root reducer
* function (either directly or indirectly by passing an object as `reducer`),
* this must be an object with the same shape as the reducer map keys.
*/
// we infer here, and instead complain if the reducer doesn't match
preloadedState?: P
/**
* The store enhancers to apply. See Redux's `createStore()`.
* All enhancers will be included before the DevTools Extension enhancer.
* If you need to customize the order of enhancers, supply a callback
* function that will receive a `getDefaultEnhancers` function that returns a Tuple,
* and should return a Tuple of enhancers (such as `getDefaultEnhancers().concat(offline)`).
* If you only need to add middleware, you can use the `middleware` parameter instead.
*/
enhancers?: (getDefaultEnhancers: GetDefaultEnhancers<M>) => E
}
export type Middlewares<S> = ReadonlyArray<Middleware<{}, S>>
type Enhancers = ReadonlyArray<StoreEnhancer>
/**
* A Redux store returned by `configureStore()`. Supports dispatching
* side-effectful _thunks_ in addition to plain actions.
*
* @public
*/
export type EnhancedStore<
S = any,
A extends Action = UnknownAction,
E extends Enhancers = Enhancers,
> = ExtractStoreExtensions<E> &
Store<S, A, UnknownIfNonSpecific<ExtractStateExtensions<E>>>
/**
* A friendly abstraction over the standard Redux `createStore()` function.
*
* @param options The store configuration.
* @returns A configured Redux store.
*
* @public
*/
export function configureStore<
S = any,
A extends Action = UnknownAction,
M extends Tuple<Middlewares<S>> = Tuple<[ThunkMiddlewareFor<S>]>,
E extends Tuple<Enhancers> = Tuple<
[StoreEnhancer<{ dispatch: ExtractDispatchExtensions<M> }>, StoreEnhancer]
>,
P = S,
>(options: ConfigureStoreOptions<S, A, M, E, P>): EnhancedStore<S, A, E> {
const getDefaultMiddleware = buildGetDefaultMiddleware<S>()
const {
reducer = undefined,
middleware,
devTools = true,
duplicateMiddlewareCheck = true,
preloadedState = undefined,
enhancers = undefined,
} = options || {}
let rootReducer: Reducer<S, A, P>
if (typeof reducer === 'function') {
rootReducer = reducer
} else if (isPlainObject(reducer)) {
rootReducer = combineReducers(reducer) as unknown as Reducer<S, A, P>
} else {
throw new Error(
'`reducer` is a required argument, and must be a function or an object of functions that can be passed to combineReducers',
)
}
if (
process.env.NODE_ENV !== 'production' &&
middleware &&
typeof middleware !== 'function'
) {
throw new Error('`middleware` field must be a callback')
}
let finalMiddleware: Tuple<Middlewares<S>>
if (typeof middleware === 'function') {
finalMiddleware = middleware(getDefaultMiddleware)
if (
process.env.NODE_ENV !== 'production' &&
!Array.isArray(finalMiddleware)
) {
throw new Error(
'when using a middleware builder function, an array of middleware must be returned',
)
}
} else {
finalMiddleware = getDefaultMiddleware()
}
if (
process.env.NODE_ENV !== 'production' &&
finalMiddleware.some((item: any) => typeof item !== 'function')
) {
throw new Error(
'each middleware provided to configureStore must be a function',
)
}
if (process.env.NODE_ENV !== 'production' && duplicateMiddlewareCheck) {
let middlewareReferences = new Set<Middleware<any, S>>()
finalMiddleware.forEach((middleware) => {
if (middlewareReferences.has(middleware)) {
throw new Error(
'Duplicate middleware references found when creating the store. Ensure that each middleware is only included once.',
)
}
middlewareReferences.add(middleware)
})
}
let finalCompose = compose
if (devTools) {
finalCompose = composeWithDevTools({
// Enable capture of stack traces for dispatched Redux actions
trace: process.env.NODE_ENV !== 'production',
...(typeof devTools === 'object' && devTools),
})
}
const middlewareEnhancer = applyMiddleware(...finalMiddleware)
const getDefaultEnhancers = buildGetDefaultEnhancers<M>(middlewareEnhancer)
if (
process.env.NODE_ENV !== 'production' &&
enhancers &&
typeof enhancers !== 'function'
) {
throw new Error('`enhancers` field must be a callback')
}
let storeEnhancers =
typeof enhancers === 'function'
? enhancers(getDefaultEnhancers)
: getDefaultEnhancers()
if (process.env.NODE_ENV !== 'production' && !Array.isArray(storeEnhancers)) {
throw new Error('`enhancers` callback must return an array')
}
if (
process.env.NODE_ENV !== 'production' &&
storeEnhancers.some((item: any) => typeof item !== 'function')
) {
throw new Error(
'each enhancer provided to configureStore must be a function',
)
}
if (
process.env.NODE_ENV !== 'production' &&
finalMiddleware.length &&
!storeEnhancers.includes(middlewareEnhancer)
) {
console.error(
'middlewares were provided, but middleware enhancer was not included in final enhancers - make sure to call `getDefaultEnhancers`',
)
}
const composedEnhancer: StoreEnhancer<any> = finalCompose(...storeEnhancers)
return createStore(rootReducer, preloadedState as P, composedEnhancer)
}

View file

@ -0,0 +1,324 @@
import { isAction } from './reduxImports'
import type {
IsUnknownOrNonInferrable,
IfMaybeUndefined,
IfVoid,
IsAny,
} from './tsHelpers'
import { hasMatchFunction } from './tsHelpers'
/**
* An action with a string type and an associated payload. This is the
* type of action returned by `createAction()` action creators.
*
* @template P The type of the action's payload.
* @template T the type used for the action type.
* @template M The type of the action's meta (optional)
* @template E The type of the action's error (optional)
*
* @public
*/
export type PayloadAction<
P = void,
T extends string = string,
M = never,
E = never,
> = {
payload: P
type: T
} & ([M] extends [never]
? {}
: {
meta: M
}) &
([E] extends [never]
? {}
: {
error: E
})
/**
* A "prepare" method to be used as the second parameter of `createAction`.
* Takes any number of arguments and returns a Flux Standard Action without
* type (will be added later) that *must* contain a payload (might be undefined).
*
* @public
*/
export type PrepareAction<P> =
| ((...args: any[]) => { payload: P })
| ((...args: any[]) => { payload: P; meta: any })
| ((...args: any[]) => { payload: P; error: any })
| ((...args: any[]) => { payload: P; meta: any; error: any })
/**
* Internal version of `ActionCreatorWithPreparedPayload`. Not to be used externally.
*
* @internal
*/
export type _ActionCreatorWithPreparedPayload<
PA extends PrepareAction<any> | void,
T extends string = string,
> =
PA extends PrepareAction<infer P>
? ActionCreatorWithPreparedPayload<
Parameters<PA>,
P,
T,
ReturnType<PA> extends {
error: infer E
}
? E
: never,
ReturnType<PA> extends {
meta: infer M
}
? M
: never
>
: void
/**
* Basic type for all action creators.
*
* @inheritdoc {redux#ActionCreator}
*/
export type BaseActionCreator<P, T extends string, M = never, E = never> = {
type: T
match: (action: unknown) => action is PayloadAction<P, T, M, E>
}
/**
* An action creator that takes multiple arguments that are passed
* to a `PrepareAction` method to create the final Action.
* @typeParam Args arguments for the action creator function
* @typeParam P `payload` type
* @typeParam T `type` name
* @typeParam E optional `error` type
* @typeParam M optional `meta` type
*
* @inheritdoc {redux#ActionCreator}
*
* @public
*/
export interface ActionCreatorWithPreparedPayload<
Args extends unknown[],
P,
T extends string = string,
E = never,
M = never,
> extends BaseActionCreator<P, T, M, E> {
/**
* Calling this {@link redux#ActionCreator} with `Args` will return
* an Action with a payload of type `P` and (depending on the `PrepareAction`
* method used) a `meta`- and `error` property of types `M` and `E` respectively.
*/
(...args: Args): PayloadAction<P, T, M, E>
}
/**
* An action creator of type `T` that takes an optional payload of type `P`.
*
* @inheritdoc {redux#ActionCreator}
*
* @public
*/
export interface ActionCreatorWithOptionalPayload<P, T extends string = string>
extends BaseActionCreator<P, T> {
/**
* Calling this {@link redux#ActionCreator} with an argument will
* return a {@link PayloadAction} of type `T` with a payload of `P`.
* Calling it without an argument will return a PayloadAction with a payload of `undefined`.
*/
(payload?: P): PayloadAction<P, T>
}
/**
* An action creator of type `T` that takes no payload.
*
* @inheritdoc {redux#ActionCreator}
*
* @public
*/
export interface ActionCreatorWithoutPayload<T extends string = string>
extends BaseActionCreator<undefined, T> {
/**
* Calling this {@link redux#ActionCreator} will
* return a {@link PayloadAction} of type `T` with a payload of `undefined`
*/
(noArgument: void): PayloadAction<undefined, T>
}
/**
* An action creator of type `T` that requires a payload of type P.
*
* @inheritdoc {redux#ActionCreator}
*
* @public
*/
export interface ActionCreatorWithPayload<P, T extends string = string>
extends BaseActionCreator<P, T> {
/**
* Calling this {@link redux#ActionCreator} with an argument will
* return a {@link PayloadAction} of type `T` with a payload of `P`
*/
(payload: P): PayloadAction<P, T>
}
/**
* An action creator of type `T` whose `payload` type could not be inferred. Accepts everything as `payload`.
*
* @inheritdoc {redux#ActionCreator}
*
* @public
*/
export interface ActionCreatorWithNonInferrablePayload<
T extends string = string,
> extends BaseActionCreator<unknown, T> {
/**
* Calling this {@link redux#ActionCreator} with an argument will
* return a {@link PayloadAction} of type `T` with a payload
* of exactly the type of the argument.
*/
<PT extends unknown>(payload: PT): PayloadAction<PT, T>
}
/**
* An action creator that produces actions with a `payload` attribute.
*
* @typeParam P the `payload` type
* @typeParam T the `type` of the resulting action
* @typeParam PA if the resulting action is preprocessed by a `prepare` method, the signature of said method.
*
* @public
*/
export type PayloadActionCreator<
P = void,
T extends string = string,
PA extends PrepareAction<P> | void = void,
> = IfPrepareActionMethodProvided<
PA,
_ActionCreatorWithPreparedPayload<PA, T>,
// else
IsAny<
P,
ActionCreatorWithPayload<any, T>,
IsUnknownOrNonInferrable<
P,
ActionCreatorWithNonInferrablePayload<T>,
// else
IfVoid<
P,
ActionCreatorWithoutPayload<T>,
// else
IfMaybeUndefined<
P,
ActionCreatorWithOptionalPayload<P, T>,
// else
ActionCreatorWithPayload<P, T>
>
>
>
>
>
/**
* A utility function to create an action creator for the given action type
* string. The action creator accepts a single argument, which will be included
* in the action object as a field called payload. The action creator function
* will also have its toString() overridden so that it returns the action type.
*
* @param type The action type to use for created actions.
* @param prepare (optional) a method that takes any number of arguments and returns { payload } or { payload, meta }.
* If this is given, the resulting action creator will pass its arguments to this method to calculate payload & meta.
*
* @public
*/
export function createAction<P = void, T extends string = string>(
type: T,
): PayloadActionCreator<P, T>
/**
* A utility function to create an action creator for the given action type
* string. The action creator accepts a single argument, which will be included
* in the action object as a field called payload. The action creator function
* will also have its toString() overridden so that it returns the action type.
*
* @param type The action type to use for created actions.
* @param prepare (optional) a method that takes any number of arguments and returns { payload } or { payload, meta }.
* If this is given, the resulting action creator will pass its arguments to this method to calculate payload & meta.
*
* @public
*/
export function createAction<
PA extends PrepareAction<any>,
T extends string = string,
>(
type: T,
prepareAction: PA,
): PayloadActionCreator<ReturnType<PA>['payload'], T, PA>
export function createAction(type: string, prepareAction?: Function): any {
function actionCreator(...args: any[]) {
if (prepareAction) {
let prepared = prepareAction(...args)
if (!prepared) {
throw new Error('prepareAction did not return an object')
}
return {
type,
payload: prepared.payload,
...('meta' in prepared && { meta: prepared.meta }),
...('error' in prepared && { error: prepared.error }),
}
}
return { type, payload: args[0] }
}
actionCreator.toString = () => `${type}`
actionCreator.type = type
actionCreator.match = (action: unknown): action is PayloadAction =>
isAction(action) && action.type === type
return actionCreator
}
/**
* Returns true if value is an RTK-like action creator, with a static type property and match method.
*/
export function isActionCreator(
action: unknown,
): action is BaseActionCreator<unknown, string> & Function {
return (
typeof action === 'function' &&
'type' in action &&
// hasMatchFunction only wants Matchers but I don't see the point in rewriting it
hasMatchFunction(action as any)
)
}
/**
* Returns true if value is an action with a string type and valid Flux Standard Action keys.
*/
export function isFSA(action: unknown): action is {
type: string
payload?: unknown
error?: unknown
meta?: unknown
} {
return isAction(action) && Object.keys(action).every(isValidKey)
}
function isValidKey(key: string) {
return ['type', 'payload', 'error', 'meta'].indexOf(key) > -1
}
// helper types for more readable typings
type IfPrepareActionMethodProvided<
PA extends PrepareAction<any> | void,
True,
False,
> = PA extends (...args: any[]) => any ? True : False

View file

@ -0,0 +1,791 @@
import type { Dispatch, UnknownAction } from 'redux'
import type { ThunkDispatch } from 'redux-thunk'
import type { ActionCreatorWithPreparedPayload } from './createAction'
import { createAction } from './createAction'
import { isAnyOf } from './matchers'
import { nanoid } from './nanoid'
import type {
FallbackIfUnknown,
Id,
IsAny,
IsUnknown,
SafePromise,
} from './tsHelpers'
export type BaseThunkAPI<
S,
E,
D extends Dispatch = Dispatch,
RejectedValue = unknown,
RejectedMeta = unknown,
FulfilledMeta = unknown,
> = {
dispatch: D
getState: () => S
extra: E
requestId: string
signal: AbortSignal
abort: (reason?: string) => void
rejectWithValue: IsUnknown<
RejectedMeta,
(value: RejectedValue) => RejectWithValue<RejectedValue, RejectedMeta>,
(
value: RejectedValue,
meta: RejectedMeta,
) => RejectWithValue<RejectedValue, RejectedMeta>
>
fulfillWithValue: IsUnknown<
FulfilledMeta,
<FulfilledValue>(value: FulfilledValue) => FulfilledValue,
<FulfilledValue>(
value: FulfilledValue,
meta: FulfilledMeta,
) => FulfillWithMeta<FulfilledValue, FulfilledMeta>
>
}
/**
* @public
*/
export interface SerializedError {
name?: string
message?: string
stack?: string
code?: string
}
const commonProperties: Array<keyof SerializedError> = [
'name',
'message',
'stack',
'code',
]
class RejectWithValue<Payload, RejectedMeta> {
/*
type-only property to distinguish between RejectWithValue and FulfillWithMeta
does not exist at runtime
*/
private readonly _type!: 'RejectWithValue'
constructor(
public readonly payload: Payload,
public readonly meta: RejectedMeta,
) {}
}
class FulfillWithMeta<Payload, FulfilledMeta> {
/*
type-only property to distinguish between RejectWithValue and FulfillWithMeta
does not exist at runtime
*/
private readonly _type!: 'FulfillWithMeta'
constructor(
public readonly payload: Payload,
public readonly meta: FulfilledMeta,
) {}
}
/**
* Serializes an error into a plain object.
* Reworked from https://github.com/sindresorhus/serialize-error
*
* @public
*/
export const miniSerializeError = (value: any): SerializedError => {
if (typeof value === 'object' && value !== null) {
const simpleError: SerializedError = {}
for (const property of commonProperties) {
if (typeof value[property] === 'string') {
simpleError[property] = value[property]
}
}
return simpleError
}
return { message: String(value) }
}
export type AsyncThunkConfig = {
state?: unknown
dispatch?: ThunkDispatch<unknown, unknown, UnknownAction>
extra?: unknown
rejectValue?: unknown
serializedErrorType?: unknown
pendingMeta?: unknown
fulfilledMeta?: unknown
rejectedMeta?: unknown
}
export type GetState<ThunkApiConfig> = ThunkApiConfig extends {
state: infer State
}
? State
: unknown
type GetExtra<ThunkApiConfig> = ThunkApiConfig extends { extra: infer Extra }
? Extra
: unknown
type GetDispatch<ThunkApiConfig> = ThunkApiConfig extends {
dispatch: infer Dispatch
}
? FallbackIfUnknown<
Dispatch,
ThunkDispatch<
GetState<ThunkApiConfig>,
GetExtra<ThunkApiConfig>,
UnknownAction
>
>
: ThunkDispatch<
GetState<ThunkApiConfig>,
GetExtra<ThunkApiConfig>,
UnknownAction
>
export type GetThunkAPI<ThunkApiConfig> = BaseThunkAPI<
GetState<ThunkApiConfig>,
GetExtra<ThunkApiConfig>,
GetDispatch<ThunkApiConfig>,
GetRejectValue<ThunkApiConfig>,
GetRejectedMeta<ThunkApiConfig>,
GetFulfilledMeta<ThunkApiConfig>
>
type GetRejectValue<ThunkApiConfig> = ThunkApiConfig extends {
rejectValue: infer RejectValue
}
? RejectValue
: unknown
type GetPendingMeta<ThunkApiConfig> = ThunkApiConfig extends {
pendingMeta: infer PendingMeta
}
? PendingMeta
: unknown
type GetFulfilledMeta<ThunkApiConfig> = ThunkApiConfig extends {
fulfilledMeta: infer FulfilledMeta
}
? FulfilledMeta
: unknown
type GetRejectedMeta<ThunkApiConfig> = ThunkApiConfig extends {
rejectedMeta: infer RejectedMeta
}
? RejectedMeta
: unknown
type GetSerializedErrorType<ThunkApiConfig> = ThunkApiConfig extends {
serializedErrorType: infer GetSerializedErrorType
}
? GetSerializedErrorType
: SerializedError
type MaybePromise<T> = T | Promise<T> | (T extends any ? Promise<T> : never)
/**
* A type describing the return value of the `payloadCreator` argument to `createAsyncThunk`.
* Might be useful for wrapping `createAsyncThunk` in custom abstractions.
*
* @public
*/
export type AsyncThunkPayloadCreatorReturnValue<
Returned,
ThunkApiConfig extends AsyncThunkConfig,
> = MaybePromise<
| IsUnknown<
GetFulfilledMeta<ThunkApiConfig>,
Returned,
FulfillWithMeta<Returned, GetFulfilledMeta<ThunkApiConfig>>
>
| RejectWithValue<
GetRejectValue<ThunkApiConfig>,
GetRejectedMeta<ThunkApiConfig>
>
>
/**
* A type describing the `payloadCreator` argument to `createAsyncThunk`.
* Might be useful for wrapping `createAsyncThunk` in custom abstractions.
*
* @public
*/
export type AsyncThunkPayloadCreator<
Returned,
ThunkArg = void,
ThunkApiConfig extends AsyncThunkConfig = {},
> = (
arg: ThunkArg,
thunkAPI: GetThunkAPI<ThunkApiConfig>,
) => AsyncThunkPayloadCreatorReturnValue<Returned, ThunkApiConfig>
/**
* A ThunkAction created by `createAsyncThunk`.
* Dispatching it returns a Promise for either a
* fulfilled or rejected action.
* Also, the returned value contains an `abort()` method
* that allows the asyncAction to be cancelled from the outside.
*
* @public
*/
export type AsyncThunkAction<
Returned,
ThunkArg,
ThunkApiConfig extends AsyncThunkConfig,
> = (
dispatch: NonNullable<GetDispatch<ThunkApiConfig>>,
getState: () => GetState<ThunkApiConfig>,
extra: GetExtra<ThunkApiConfig>,
) => SafePromise<
| ReturnType<AsyncThunkFulfilledActionCreator<Returned, ThunkArg>>
| ReturnType<AsyncThunkRejectedActionCreator<ThunkArg, ThunkApiConfig>>
> & {
abort: (reason?: string) => void
requestId: string
arg: ThunkArg
unwrap: () => Promise<Returned>
}
/**
* Config provided when calling the async thunk action creator.
*/
export interface AsyncThunkDispatchConfig {
/**
* An external `AbortSignal` that will be tracked by the internal `AbortSignal`.
*/
signal?: AbortSignal
}
type AsyncThunkActionCreator<
Returned,
ThunkArg,
ThunkApiConfig extends AsyncThunkConfig,
> = IsAny<
ThunkArg,
// any handling
(
arg: ThunkArg,
config?: AsyncThunkDispatchConfig,
) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig>,
// unknown handling
unknown extends ThunkArg
? (
arg: ThunkArg,
config?: AsyncThunkDispatchConfig,
) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> // argument not specified or specified as void or undefined
: [ThunkArg] extends [void] | [undefined]
? (
arg?: undefined,
config?: AsyncThunkDispatchConfig,
) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> // argument contains void
: [void] extends [ThunkArg] // make optional
? (
arg?: ThunkArg,
config?: AsyncThunkDispatchConfig,
) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> // argument contains undefined
: [undefined] extends [ThunkArg]
? WithStrictNullChecks<
// with strict nullChecks: make optional
(
arg?: ThunkArg,
config?: AsyncThunkDispatchConfig,
) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig>,
// without strict null checks this will match everything, so don't make it optional
(
arg: ThunkArg,
config?: AsyncThunkDispatchConfig,
) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig>
> // default case: normal argument
: (
arg: ThunkArg,
config?: AsyncThunkDispatchConfig,
) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig>
>
/**
* Options object for `createAsyncThunk`.
*
* @public
*/
export type AsyncThunkOptions<
ThunkArg = void,
ThunkApiConfig extends AsyncThunkConfig = {},
> = {
/**
* A method to control whether the asyncThunk should be executed. Has access to the
* `arg`, `api.getState()` and `api.extra` arguments.
*
* @returns `false` if it should be skipped
*/
condition?(
arg: ThunkArg,
api: Pick<GetThunkAPI<ThunkApiConfig>, 'getState' | 'extra'>,
): MaybePromise<boolean | undefined>
/**
* If `condition` returns `false`, the asyncThunk will be skipped.
* This option allows you to control whether a `rejected` action with `meta.condition == false`
* will be dispatched or not.
*
* @default `false`
*/
dispatchConditionRejection?: boolean
serializeError?: (x: unknown) => GetSerializedErrorType<ThunkApiConfig>
/**
* A function to use when generating the `requestId` for the request sequence.
*
* @default `nanoid`
*/
idGenerator?: (arg: ThunkArg) => string
} & IsUnknown<
GetPendingMeta<ThunkApiConfig>,
{
/**
* A method to generate additional properties to be added to `meta` of the pending action.
*
* Using this optional overload will not modify the types correctly, this overload is only in place to support JavaScript users.
* Please use the `ThunkApiConfig` parameter `pendingMeta` to get access to a correctly typed overload
*/
getPendingMeta?(
base: {
arg: ThunkArg
requestId: string
},
api: Pick<GetThunkAPI<ThunkApiConfig>, 'getState' | 'extra'>,
): GetPendingMeta<ThunkApiConfig>
},
{
/**
* A method to generate additional properties to be added to `meta` of the pending action.
*/
getPendingMeta(
base: {
arg: ThunkArg
requestId: string
},
api: Pick<GetThunkAPI<ThunkApiConfig>, 'getState' | 'extra'>,
): GetPendingMeta<ThunkApiConfig>
}
>
export type AsyncThunkPendingActionCreator<
ThunkArg,
ThunkApiConfig = {},
> = ActionCreatorWithPreparedPayload<
[string, ThunkArg, GetPendingMeta<ThunkApiConfig>?],
undefined,
string,
never,
{
arg: ThunkArg
requestId: string
requestStatus: 'pending'
} & GetPendingMeta<ThunkApiConfig>
>
export type AsyncThunkRejectedActionCreator<
ThunkArg,
ThunkApiConfig = {},
> = ActionCreatorWithPreparedPayload<
[
Error | null,
string,
ThunkArg,
GetRejectValue<ThunkApiConfig>?,
GetRejectedMeta<ThunkApiConfig>?,
],
GetRejectValue<ThunkApiConfig> | undefined,
string,
GetSerializedErrorType<ThunkApiConfig>,
{
arg: ThunkArg
requestId: string
requestStatus: 'rejected'
aborted: boolean
condition: boolean
} & (
| ({ rejectedWithValue: false } & {
[K in keyof GetRejectedMeta<ThunkApiConfig>]?: undefined
})
| ({ rejectedWithValue: true } & GetRejectedMeta<ThunkApiConfig>)
)
>
export type AsyncThunkFulfilledActionCreator<
Returned,
ThunkArg,
ThunkApiConfig = {},
> = ActionCreatorWithPreparedPayload<
[Returned, string, ThunkArg, GetFulfilledMeta<ThunkApiConfig>?],
Returned,
string,
never,
{
arg: ThunkArg
requestId: string
requestStatus: 'fulfilled'
} & GetFulfilledMeta<ThunkApiConfig>
>
/**
* A type describing the return value of `createAsyncThunk`.
* Might be useful for wrapping `createAsyncThunk` in custom abstractions.
*
* @public
*/
export type AsyncThunk<
Returned,
ThunkArg,
ThunkApiConfig extends AsyncThunkConfig,
> = AsyncThunkActionCreator<Returned, ThunkArg, ThunkApiConfig> & {
pending: AsyncThunkPendingActionCreator<ThunkArg, ThunkApiConfig>
rejected: AsyncThunkRejectedActionCreator<ThunkArg, ThunkApiConfig>
fulfilled: AsyncThunkFulfilledActionCreator<
Returned,
ThunkArg,
ThunkApiConfig
>
// matchSettled?
settled: (
action: any,
) => action is ReturnType<
| AsyncThunkRejectedActionCreator<ThunkArg, ThunkApiConfig>
| AsyncThunkFulfilledActionCreator<Returned, ThunkArg, ThunkApiConfig>
>
typePrefix: string
}
export type OverrideThunkApiConfigs<OldConfig, NewConfig> = Id<
NewConfig & Omit<OldConfig, keyof NewConfig>
>
export type CreateAsyncThunkFunction<
CurriedThunkApiConfig extends AsyncThunkConfig,
> = {
/**
*
* @param typePrefix
* @param payloadCreator
* @param options
*
* @public
*/
// separate signature without `AsyncThunkConfig` for better inference
<Returned, ThunkArg = void>(
typePrefix: string,
payloadCreator: AsyncThunkPayloadCreator<
Returned,
ThunkArg,
CurriedThunkApiConfig
>,
options?: AsyncThunkOptions<ThunkArg, CurriedThunkApiConfig>,
): AsyncThunk<Returned, ThunkArg, CurriedThunkApiConfig>
/**
*
* @param typePrefix
* @param payloadCreator
* @param options
*
* @public
*/
<Returned, ThunkArg, ThunkApiConfig extends AsyncThunkConfig>(
typePrefix: string,
payloadCreator: AsyncThunkPayloadCreator<
Returned,
ThunkArg,
OverrideThunkApiConfigs<CurriedThunkApiConfig, ThunkApiConfig>
>,
options?: AsyncThunkOptions<
ThunkArg,
OverrideThunkApiConfigs<CurriedThunkApiConfig, ThunkApiConfig>
>,
): AsyncThunk<
Returned,
ThunkArg,
OverrideThunkApiConfigs<CurriedThunkApiConfig, ThunkApiConfig>
>
}
type CreateAsyncThunk<CurriedThunkApiConfig extends AsyncThunkConfig> =
CreateAsyncThunkFunction<CurriedThunkApiConfig> & {
withTypes<ThunkApiConfig extends AsyncThunkConfig>(): CreateAsyncThunk<
OverrideThunkApiConfigs<CurriedThunkApiConfig, ThunkApiConfig>
>
}
const externalAbortMessage = 'External signal was aborted'
export const createAsyncThunk = /* @__PURE__ */ (() => {
function createAsyncThunk<
Returned,
ThunkArg,
ThunkApiConfig extends AsyncThunkConfig,
>(
typePrefix: string,
payloadCreator: AsyncThunkPayloadCreator<
Returned,
ThunkArg,
ThunkApiConfig
>,
options?: AsyncThunkOptions<ThunkArg, ThunkApiConfig>,
): AsyncThunk<Returned, ThunkArg, ThunkApiConfig> {
type RejectedValue = GetRejectValue<ThunkApiConfig>
type PendingMeta = GetPendingMeta<ThunkApiConfig>
type FulfilledMeta = GetFulfilledMeta<ThunkApiConfig>
type RejectedMeta = GetRejectedMeta<ThunkApiConfig>
const fulfilled: AsyncThunkFulfilledActionCreator<
Returned,
ThunkArg,
ThunkApiConfig
> = createAction(
typePrefix + '/fulfilled',
(
payload: Returned,
requestId: string,
arg: ThunkArg,
meta?: FulfilledMeta,
) => ({
payload,
meta: {
...((meta as any) || {}),
arg,
requestId,
requestStatus: 'fulfilled' as const,
},
}),
)
const pending: AsyncThunkPendingActionCreator<ThunkArg, ThunkApiConfig> =
createAction(
typePrefix + '/pending',
(requestId: string, arg: ThunkArg, meta?: PendingMeta) => ({
payload: undefined,
meta: {
...((meta as any) || {}),
arg,
requestId,
requestStatus: 'pending' as const,
},
}),
)
const rejected: AsyncThunkRejectedActionCreator<ThunkArg, ThunkApiConfig> =
createAction(
typePrefix + '/rejected',
(
error: Error | null,
requestId: string,
arg: ThunkArg,
payload?: RejectedValue,
meta?: RejectedMeta,
) => ({
payload,
error: ((options && options.serializeError) || miniSerializeError)(
error || 'Rejected',
) as GetSerializedErrorType<ThunkApiConfig>,
meta: {
...((meta as any) || {}),
arg,
requestId,
rejectedWithValue: !!payload,
requestStatus: 'rejected' as const,
aborted: error?.name === 'AbortError',
condition: error?.name === 'ConditionError',
},
}),
)
function actionCreator(
arg: ThunkArg,
{ signal }: AsyncThunkDispatchConfig = {},
): AsyncThunkAction<Returned, ThunkArg, Required<ThunkApiConfig>> {
return (dispatch, getState, extra) => {
const requestId = options?.idGenerator
? options.idGenerator(arg)
: nanoid()
const abortController = new AbortController()
let abortHandler: (() => void) | undefined
let abortReason: string | undefined
function abort(reason?: string) {
abortReason = reason
abortController.abort()
}
if (signal) {
if (signal.aborted) {
abort(externalAbortMessage)
} else {
signal.addEventListener(
'abort',
() => abort(externalAbortMessage),
{ once: true },
)
}
}
const promise = (async function () {
let finalAction: ReturnType<typeof fulfilled | typeof rejected>
try {
let conditionResult = options?.condition?.(arg, { getState, extra })
if (isThenable(conditionResult)) {
conditionResult = await conditionResult
}
if (conditionResult === false || abortController.signal.aborted) {
// eslint-disable-next-line no-throw-literal
throw {
name: 'ConditionError',
message: 'Aborted due to condition callback returning false.',
}
}
const abortedPromise = new Promise<never>((_, reject) => {
abortHandler = () => {
reject({
name: 'AbortError',
message: abortReason || 'Aborted',
})
}
abortController.signal.addEventListener('abort', abortHandler, {
once: true,
})
})
dispatch(
pending(
requestId,
arg,
options?.getPendingMeta?.(
{ requestId, arg },
{ getState, extra },
),
) as any,
)
finalAction = await Promise.race([
abortedPromise,
Promise.resolve(
payloadCreator(arg, {
dispatch,
getState,
extra,
requestId,
signal: abortController.signal,
abort,
rejectWithValue: ((
value: RejectedValue,
meta?: RejectedMeta,
) => {
return new RejectWithValue(value, meta)
}) as any,
fulfillWithValue: ((value: unknown, meta?: FulfilledMeta) => {
return new FulfillWithMeta(value, meta)
}) as any,
}),
).then((result) => {
if (result instanceof RejectWithValue) {
throw result
}
if (result instanceof FulfillWithMeta) {
return fulfilled(result.payload, requestId, arg, result.meta)
}
return fulfilled(result as any, requestId, arg)
}),
])
} catch (err) {
finalAction =
err instanceof RejectWithValue
? rejected(null, requestId, arg, err.payload, err.meta)
: rejected(err as any, requestId, arg)
} finally {
if (abortHandler) {
abortController.signal.removeEventListener('abort', abortHandler)
}
}
// We dispatch the result action _after_ the catch, to avoid having any errors
// here get swallowed by the try/catch block,
// per https://twitter.com/dan_abramov/status/770914221638942720
// and https://github.com/reduxjs/redux-toolkit/blob/e85eb17b39a2118d859f7b7746e0f3fee523e089/docs/tutorials/advanced-tutorial.md#async-error-handling-logic-in-thunks
const skipDispatch =
options &&
!options.dispatchConditionRejection &&
rejected.match(finalAction) &&
(finalAction as any).meta.condition
if (!skipDispatch) {
dispatch(finalAction as any)
}
return finalAction
})()
return Object.assign(promise as SafePromise<any>, {
abort,
requestId,
arg,
unwrap() {
return promise.then<any>(unwrapResult)
},
})
}
}
return Object.assign(
actionCreator as AsyncThunkActionCreator<
Returned,
ThunkArg,
ThunkApiConfig
>,
{
pending,
rejected,
fulfilled,
settled: isAnyOf(rejected, fulfilled),
typePrefix,
},
)
}
createAsyncThunk.withTypes = () => createAsyncThunk
return createAsyncThunk as CreateAsyncThunk<AsyncThunkConfig>
})()
interface UnwrappableAction {
payload: any
meta?: any
error?: any
}
type UnwrappedActionPayload<T extends UnwrappableAction> = Exclude<
T,
{ error: any }
>['payload']
/**
* @public
*/
export function unwrapResult<R extends UnwrappableAction>(
action: R,
): UnwrappedActionPayload<R> {
if (action.meta && action.meta.rejectedWithValue) {
throw action.payload
}
if (action.error) {
throw action.error
}
return action.payload
}
type WithStrictNullChecks<True, False> = undefined extends boolean
? False
: True
function isThenable(value: any): value is PromiseLike<any> {
return (
value !== null &&
typeof value === 'object' &&
typeof value.then === 'function'
)
}

View file

@ -0,0 +1,30 @@
import { current, isDraft } from './immerImports'
import { createSelectorCreator, weakMapMemoize } from './reselectImports'
export const createDraftSafeSelectorCreator: typeof createSelectorCreator = (
...args: unknown[]
) => {
const createSelector = (createSelectorCreator as any)(...args)
const createDraftSafeSelector = Object.assign(
(...args: unknown[]) => {
const selector = createSelector(...args)
const wrappedSelector = (value: unknown, ...rest: unknown[]) =>
selector(isDraft(value) ? current(value) : value, ...rest)
Object.assign(wrappedSelector, selector)
return wrappedSelector as any
},
{ withTypes: () => createDraftSafeSelector },
)
return createDraftSafeSelector
}
/**
* "Draft-Safe" version of `reselect`'s `createSelector`:
* If an `immer`-drafted object is passed into the resulting selector's first argument,
* the selector will act on the current draft value, instead of returning a cached value
* that might be possibly outdated if the draft has been modified since.
* @public
*/
export const createDraftSafeSelector =
/* @__PURE__ */
createDraftSafeSelectorCreator(weakMapMemoize)

View file

@ -0,0 +1,217 @@
import type { Draft } from 'immer'
import type { Action, Reducer, UnknownAction } from 'redux'
import { createNextState, isDraft, isDraftable } from './immerImports'
import type { ActionReducerMapBuilder } from './mapBuilders'
import { executeReducerBuilderCallback } from './mapBuilders'
import type { TypeGuard } from './tsHelpers'
import { freezeDraftable } from './utils'
/**
* Defines a mapping from action types to corresponding action object shapes.
*
* @deprecated This should not be used manually - it is only used for internal
* inference purposes and should not have any further value.
* It might be removed in the future.
* @public
*/
export type Actions<T extends keyof any = string> = Record<T, Action>
export type ActionMatcherDescription<S, A extends Action> = {
matcher: TypeGuard<A>
reducer: CaseReducer<S, NoInfer<A>>
}
export type ReadonlyActionMatcherDescriptionCollection<S> = ReadonlyArray<
ActionMatcherDescription<S, any>
>
export type ActionMatcherDescriptionCollection<S> = Array<
ActionMatcherDescription<S, any>
>
/**
* A *case reducer* is a reducer function for a specific action type. Case
* reducers can be composed to full reducers using `createReducer()`.
*
* Unlike a normal Redux reducer, a case reducer is never called with an
* `undefined` state to determine the initial state. Instead, the initial
* state is explicitly specified as an argument to `createReducer()`.
*
* In addition, a case reducer can choose to mutate the passed-in `state`
* value directly instead of returning a new state. This does not actually
* cause the store state to be mutated directly; instead, thanks to
* [immer](https://github.com/mweststrate/immer), the mutations are
* translated to copy operations that result in a new state.
*
* @public
*/
export type CaseReducer<S = any, A extends Action = UnknownAction> = (
state: Draft<S>,
action: A,
) => NoInfer<S> | void | Draft<NoInfer<S>>
/**
* A mapping from action types to case reducers for `createReducer()`.
*
* @deprecated This should not be used manually - it is only used
* for internal inference purposes and using it manually
* would lead to type erasure.
* It might be removed in the future.
* @public
*/
export type CaseReducers<S, AS extends Actions> = {
[T in keyof AS]: AS[T] extends Action ? CaseReducer<S, AS[T]> : void
}
export type NotFunction<T> = T extends Function ? never : T
function isStateFunction<S>(x: unknown): x is () => S {
return typeof x === 'function'
}
export type ReducerWithInitialState<S extends NotFunction<any>> = Reducer<S> & {
getInitialState: () => S
}
/**
* A utility function that allows defining a reducer as a mapping from action
* type to *case reducer* functions that handle these action types. The
* reducer's initial state is passed as the first argument.
*
* @remarks
* The body of every case reducer is implicitly wrapped with a call to
* `produce()` from the [immer](https://github.com/mweststrate/immer) library.
* This means that rather than returning a new state object, you can also
* mutate the passed-in state object directly; these mutations will then be
* automatically and efficiently translated into copies, giving you both
* convenience and immutability.
*
* @overloadSummary
* This function accepts a callback that receives a `builder` object as its argument.
* That builder provides `addCase`, `addMatcher` and `addDefaultCase` functions that may be
* called to define what actions this reducer will handle.
*
* @param initialState - `State | (() => State)`: The initial state that should be used when the reducer is called the first time. This may also be a "lazy initializer" function, which should return an initial state value when called. This will be used whenever the reducer is called with `undefined` as its state value, and is primarily useful for cases like reading initial state from `localStorage`.
* @param builderCallback - `(builder: Builder) => void` A callback that receives a *builder* object to define
* case reducers via calls to `builder.addCase(actionCreatorOrType, reducer)`.
* @example
* ```ts
* import type { PayloadAction, UnknownAction } from '@reduxjs/toolkit';
* import { createAction, createReducer } from '@reduxjs/toolkit';
*
* const increment = createAction<number>('increment');
* const decrement = createAction<number>('decrement');
*
* function isActionWithNumberPayload(
* action: UnknownAction,
* ): action is PayloadAction<number> {
* return typeof action.payload === 'number';
* }
*
* const reducer = createReducer(
* {
* counter: 0,
* sumOfNumberPayloads: 0,
* unhandledActions: 0,
* },
* (builder) => {
* builder
* .addCase(increment, (state, action) => {
* // action is inferred correctly here
* state.counter += action.payload;
* })
* // You can chain calls, or have separate `builder.addCase()` lines each time
* .addCase(decrement, (state, action) => {
* state.counter -= action.payload;
* })
* // You can apply a "matcher function" to incoming actions
* .addMatcher(isActionWithNumberPayload, (state, action) => {})
* // and provide a default case if no other handlers matched
* .addDefaultCase((state, action) => {});
* },
* );
* ```
* @public
*/
export function createReducer<S extends NotFunction<any>>(
initialState: S | (() => S),
mapOrBuilderCallback: (builder: ActionReducerMapBuilder<S>) => void,
): ReducerWithInitialState<S> {
if (process.env.NODE_ENV !== 'production') {
if (typeof mapOrBuilderCallback === 'object') {
throw new Error(
"The object notation for `createReducer` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createReducer",
)
}
}
let [actionsMap, finalActionMatchers, finalDefaultCaseReducer] =
executeReducerBuilderCallback(mapOrBuilderCallback)
// Ensure the initial state gets frozen either way (if draftable)
let getInitialState: () => S
if (isStateFunction(initialState)) {
getInitialState = () => freezeDraftable(initialState())
} else {
const frozenInitialState = freezeDraftable(initialState)
getInitialState = () => frozenInitialState
}
function reducer(state = getInitialState(), action: any): S {
let caseReducers = [
actionsMap[action.type],
...finalActionMatchers
.filter(({ matcher }) => matcher(action))
.map(({ reducer }) => reducer),
]
if (caseReducers.filter((cr) => !!cr).length === 0) {
caseReducers = [finalDefaultCaseReducer]
}
return caseReducers.reduce((previousState, caseReducer): S => {
if (caseReducer) {
if (isDraft(previousState)) {
// If it's already a draft, we must already be inside a `createNextState` call,
// likely because this is being wrapped in `createReducer`, `createSlice`, or nested
// inside an existing draft. It's safe to just pass the draft to the mutator.
const draft = previousState as Draft<S> // We can assume this is already a draft
const result = caseReducer(draft, action)
if (result === undefined) {
return previousState
}
return result as S
} else if (!isDraftable(previousState)) {
// If state is not draftable (ex: a primitive, such as 0), we want to directly
// return the caseReducer func and not wrap it with produce.
const result = caseReducer(previousState as any, action)
if (result === undefined) {
if (previousState === null) {
return previousState
}
throw Error(
'A case reducer on a non-draftable value must not return undefined',
)
}
return result as S
} else {
// @ts-ignore createNextState() produces an Immutable<Draft<S>> rather
// than an Immutable<S>, and TypeScript cannot find out how to reconcile
// these two types.
return createNextState(previousState, (draft: Draft<S>) => {
return caseReducer(draft, action)
})
}
}
return previousState
}, state)
}
reducer.getInitialState = getInitialState
return reducer as ReducerWithInitialState<S>
}

1079
frontend/node_modules/@reduxjs/toolkit/src/createSlice.ts generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,241 @@
import type { Action, ActionCreator, StoreEnhancer } from 'redux'
import { compose } from './reduxImports'
/**
* @public
*/
export interface DevToolsEnhancerOptions {
/**
* the instance name to be showed on the monitor page. Default value is `document.title`.
* If not specified and there's no document title, it will consist of `tabId` and `instanceId`.
*/
name?: string
/**
* action creators functions to be available in the Dispatcher.
*/
actionCreators?: ActionCreator<any>[] | { [key: string]: ActionCreator<any> }
/**
* if more than one action is dispatched in the indicated interval, all new actions will be collected and sent at once.
* It is the joint between performance and speed. When set to `0`, all actions will be sent instantly.
* Set it to a higher value when experiencing perf issues (also `maxAge` to a lower value).
*
* @default 500 ms.
*/
latency?: number
/**
* (> 1) - maximum allowed actions to be stored in the history tree. The oldest actions are removed once maxAge is reached. It's critical for performance.
*
* @default 50
*/
maxAge?: number
/**
* Customizes how actions and state are serialized and deserialized. Can be a boolean or object. If given a boolean, the behavior is the same as if you
* were to pass an object and specify `options` as a boolean. Giving an object allows fine-grained customization using the `replacer` and `reviver`
* functions.
*/
serialize?:
| boolean
| {
/**
* - `undefined` - will use regular `JSON.stringify` to send data (it's the fast mode).
* - `false` - will handle also circular references.
* - `true` - will handle also date, regex, undefined, error objects, symbols, maps, sets and functions.
* - object, which contains `date`, `regex`, `undefined`, `error`, `symbol`, `map`, `set` and `function` keys.
* For each of them you can indicate if to include (by setting as `true`).
* For `function` key you can also specify a custom function which handles serialization.
* See [`jsan`](https://github.com/kolodny/jsan) for more details.
*/
options?:
| undefined
| boolean
| {
date?: true
regex?: true
undefined?: true
error?: true
symbol?: true
map?: true
set?: true
function?: true | ((fn: (...args: any[]) => any) => string)
}
/**
* [JSON replacer function](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter) used for both actions and states stringify.
* In addition, you can specify a data type by adding a [`__serializedType__`](https://github.com/zalmoxisus/remotedev-serialize/blob/master/helpers/index.js#L4)
* key. So you can deserialize it back while importing or persisting data.
* Moreover, it will also [show a nice preview showing the provided custom type](https://cloud.githubusercontent.com/assets/7957859/21814330/a17d556a-d761-11e6-85ef-159dd12f36c5.png):
*/
replacer?: (key: string, value: unknown) => any
/**
* [JSON `reviver` function](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter)
* used for parsing the imported actions and states. See [`remotedev-serialize`](https://github.com/zalmoxisus/remotedev-serialize/blob/master/immutable/serialize.js#L8-L41)
* as an example on how to serialize special data types and get them back.
*/
reviver?: (key: string, value: unknown) => any
/**
* Automatically serialize/deserialize immutablejs via [remotedev-serialize](https://github.com/zalmoxisus/remotedev-serialize).
* Just pass the Immutable library. It will support all ImmutableJS structures. You can even export them into a file and get them back.
* The only exception is `Record` class, for which you should pass this in addition the references to your classes in `refs`.
*/
immutable?: any
/**
* ImmutableJS `Record` classes used to make possible restore its instances back when importing, persisting...
*/
refs?: any
}
/**
* function which takes `action` object and id number as arguments, and should return `action` object back.
*/
actionSanitizer?: <A extends Action>(action: A, id: number) => A
/**
* function which takes `state` object and index as arguments, and should return `state` object back.
*/
stateSanitizer?: <S>(state: S, index: number) => S
/**
* *string or array of strings as regex* - actions types to be hidden / shown in the monitors (while passed to the reducers).
* If `actionsAllowlist` specified, `actionsDenylist` is ignored.
*/
actionsDenylist?: string | string[]
/**
* *string or array of strings as regex* - actions types to be hidden / shown in the monitors (while passed to the reducers).
* If `actionsAllowlist` specified, `actionsDenylist` is ignored.
*/
actionsAllowlist?: string | string[]
/**
* called for every action before sending, takes `state` and `action` object, and returns `true` in case it allows sending the current data to the monitor.
* Use it as a more advanced version of `actionsDenylist`/`actionsAllowlist` parameters.
*/
predicate?: <S, A extends Action>(state: S, action: A) => boolean
/**
* if specified as `false`, it will not record the changes till clicking on `Start recording` button.
* Available only for Redux enhancer, for others use `autoPause`.
*
* @default true
*/
shouldRecordChanges?: boolean
/**
* if specified, whenever clicking on `Pause recording` button and there are actions in the history log, will add this action type.
* If not specified, will commit when paused. Available only for Redux enhancer.
*
* @default "@@PAUSED""
*/
pauseActionType?: string
/**
* auto pauses when the extensions window is not opened, and so has zero impact on your app when not in use.
* Not available for Redux enhancer (as it already does it but storing the data to be sent).
*
* @default false
*/
autoPause?: boolean
/**
* if specified as `true`, it will not allow any non-monitor actions to be dispatched till clicking on `Unlock changes` button.
* Available only for Redux enhancer.
*
* @default false
*/
shouldStartLocked?: boolean
/**
* if set to `false`, will not recompute the states on hot reloading (or on replacing the reducers). Available only for Redux enhancer.
*
* @default true
*/
shouldHotReload?: boolean
/**
* if specified as `true`, whenever there's an exception in reducers, the monitors will show the error message, and next actions will not be dispatched.
*
* @default false
*/
shouldCatchErrors?: boolean
/**
* If you want to restrict the extension, specify the features you allow.
* If not specified, all of the features are enabled. When set as an object, only those included as `true` will be allowed.
* Note that except `true`/`false`, `import` and `export` can be set as `custom` (which is by default for Redux enhancer), meaning that the importing/exporting occurs on the client side.
* Otherwise, you'll get/set the data right from the monitor part.
*/
features?: {
/**
* start/pause recording of dispatched actions
*/
pause?: boolean
/**
* lock/unlock dispatching actions and side effects
*/
lock?: boolean
/**
* persist states on page reloading
*/
persist?: boolean
/**
* export history of actions in a file
*/
export?: boolean | 'custom'
/**
* import history of actions from a file
*/
import?: boolean | 'custom'
/**
* jump back and forth (time traveling)
*/
jump?: boolean
/**
* skip (cancel) actions
*/
skip?: boolean
/**
* drag and drop actions in the history list
*/
reorder?: boolean
/**
* dispatch custom actions or action creators
*/
dispatch?: boolean
/**
* generate tests for the selected actions
*/
test?: boolean
}
/**
* Set to true or a stacktrace-returning function to record call stack traces for dispatched actions.
* Defaults to false.
*/
trace?: boolean | (<A extends Action>(action: A) => string)
/**
* The maximum number of stack trace entries to record per action. Defaults to 10.
*/
traceLimit?: number
}
type Compose = typeof compose
interface ComposeWithDevTools {
(options: DevToolsEnhancerOptions): Compose
<StoreExt extends {}>(
...funcs: StoreEnhancer<StoreExt>[]
): StoreEnhancer<StoreExt>
}
/**
* @public
*/
export const composeWithDevTools: ComposeWithDevTools =
typeof window !== 'undefined' &&
(window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
: function () {
if (arguments.length === 0) return undefined
if (typeof arguments[0] === 'object') return compose
return compose.apply(null, arguments as any as Function[])
}
/**
* @public
*/
export const devToolsEnhancer: {
(options: DevToolsEnhancerOptions): StoreEnhancer<any>
} =
typeof window !== 'undefined' && (window as any).__REDUX_DEVTOOLS_EXTENSION__
? (window as any).__REDUX_DEVTOOLS_EXTENSION__
: function () {
return function (noop) {
return noop
}
}

View file

@ -0,0 +1,93 @@
import type { Dispatch, Middleware, UnknownAction } from 'redux'
import { compose } from '../reduxImports'
import { createAction } from '../createAction'
import { isAllOf } from '../matchers'
import { nanoid } from '../nanoid'
import { getOrInsertComputed } from '../utils'
import type {
AddMiddleware,
DynamicMiddleware,
DynamicMiddlewareInstance,
MiddlewareEntry,
WithMiddleware,
} from './types'
export type {
DynamicMiddlewareInstance,
GetDispatchType as GetDispatch,
MiddlewareApiConfig,
} from './types'
const createMiddlewareEntry = <
State = any,
DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>,
>(
middleware: Middleware<any, State, DispatchType>,
): MiddlewareEntry<State, DispatchType> => ({
middleware,
applied: new Map(),
})
const matchInstance =
(instanceId: string) =>
(action: any): action is { meta: { instanceId: string } } =>
action?.meta?.instanceId === instanceId
export const createDynamicMiddleware = <
State = any,
DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>,
>(): DynamicMiddlewareInstance<State, DispatchType> => {
const instanceId = nanoid()
const middlewareMap = new Map<
Middleware<any, State, DispatchType>,
MiddlewareEntry<State, DispatchType>
>()
const withMiddleware = Object.assign(
createAction(
'dynamicMiddleware/add',
(...middlewares: Middleware<any, State, DispatchType>[]) => ({
payload: middlewares,
meta: {
instanceId,
},
}),
),
{ withTypes: () => withMiddleware },
) as WithMiddleware<State, DispatchType>
const addMiddleware = Object.assign(
function addMiddleware(
...middlewares: Middleware<any, State, DispatchType>[]
) {
middlewares.forEach((middleware) => {
getOrInsertComputed(middlewareMap, middleware, createMiddlewareEntry)
})
},
{ withTypes: () => addMiddleware },
) as AddMiddleware<State, DispatchType>
const getFinalMiddleware: Middleware<{}, State, DispatchType> = (api) => {
const appliedMiddleware = Array.from(middlewareMap.values()).map((entry) =>
getOrInsertComputed(entry.applied, api, entry.middleware),
)
return compose(...appliedMiddleware)
}
const isWithMiddleware = isAllOf(withMiddleware, matchInstance(instanceId))
const middleware: DynamicMiddleware<State, DispatchType> =
(api) => (next) => (action) => {
if (isWithMiddleware(action)) {
addMiddleware(...action.payload)
return api.dispatch
}
return getFinalMiddleware(api)(next)(action)
}
return {
middleware,
addMiddleware,
withMiddleware,
instanceId,
}
}

View file

@ -0,0 +1,101 @@
import type {
DynamicMiddlewareInstance,
GetDispatch,
GetState,
MiddlewareApiConfig,
TSHelpersExtractDispatchExtensions,
} from '@reduxjs/toolkit'
import { createDynamicMiddleware as cDM } from '@reduxjs/toolkit'
import type { Context } from 'react'
import type { ReactReduxContextValue } from 'react-redux'
import {
createDispatchHook,
ReactReduxContext,
useDispatch as useDefaultDispatch,
} from 'react-redux'
import type { Action, Dispatch, Middleware, UnknownAction } from 'redux'
export type UseDispatchWithMiddlewareHook<
Middlewares extends Middleware<any, State, DispatchType>[] = [],
State = any,
DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>,
> = () => TSHelpersExtractDispatchExtensions<Middlewares> & DispatchType
export type CreateDispatchWithMiddlewareHook<
State = any,
DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>,
> = {
<
Middlewares extends [
Middleware<any, State, DispatchType>,
...Middleware<any, State, DispatchType>[],
],
>(
...middlewares: Middlewares
): UseDispatchWithMiddlewareHook<Middlewares, State, DispatchType>
withTypes<
MiddlewareConfig extends MiddlewareApiConfig,
>(): CreateDispatchWithMiddlewareHook<
GetState<MiddlewareConfig>,
GetDispatch<MiddlewareConfig>
>
}
type ActionFromDispatch<DispatchType extends Dispatch<Action>> =
DispatchType extends Dispatch<infer Action> ? Action : never
type ReactDynamicMiddlewareInstance<
State = any,
DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>,
> = DynamicMiddlewareInstance<State, DispatchType> & {
createDispatchWithMiddlewareHookFactory: (
context?: Context<ReactReduxContextValue<
State,
ActionFromDispatch<DispatchType>
> | null>,
) => CreateDispatchWithMiddlewareHook<State, DispatchType>
createDispatchWithMiddlewareHook: CreateDispatchWithMiddlewareHook<
State,
DispatchType
>
}
export const createDynamicMiddleware = <
State = any,
DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>,
>(): ReactDynamicMiddlewareInstance<State, DispatchType> => {
const instance = cDM<State, DispatchType>()
const createDispatchWithMiddlewareHookFactory = (
// @ts-ignore
context: Context<ReactReduxContextValue<
State,
ActionFromDispatch<DispatchType>
> | null> = ReactReduxContext,
) => {
const useDispatch =
context === ReactReduxContext
? useDefaultDispatch
: createDispatchHook(context)
function createDispatchWithMiddlewareHook<
Middlewares extends Middleware<any, State, DispatchType>[],
>(...middlewares: Middlewares) {
instance.addMiddleware(...middlewares)
return useDispatch
}
createDispatchWithMiddlewareHook.withTypes = () =>
createDispatchWithMiddlewareHook
return createDispatchWithMiddlewareHook as CreateDispatchWithMiddlewareHook<
State,
DispatchType
>
}
const createDispatchWithMiddlewareHook =
createDispatchWithMiddlewareHookFactory()
return {
...instance,
createDispatchWithMiddlewareHookFactory,
createDispatchWithMiddlewareHook,
}
}

View file

@ -0,0 +1,82 @@
import type { Dispatch, Middleware, MiddlewareAPI, UnknownAction } from 'redux'
import type { BaseActionCreator, PayloadAction } from '../createAction'
import type { GetState } from '../createAsyncThunk'
import type { ExtractDispatchExtensions, FallbackIfUnknown } from '../tsHelpers'
export type GetMiddlewareApi<MiddlewareApiConfig> = MiddlewareAPI<
GetDispatchType<MiddlewareApiConfig>,
GetState<MiddlewareApiConfig>
>
export type MiddlewareApiConfig = {
state?: unknown
dispatch?: Dispatch
}
// TODO: consolidate with cAT helpers?
export type GetDispatchType<MiddlewareApiConfig> = MiddlewareApiConfig extends {
dispatch: infer DispatchType
}
? FallbackIfUnknown<DispatchType, Dispatch>
: Dispatch
export type AddMiddleware<
State = any,
DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>,
> = {
(...middlewares: Middleware<any, State, DispatchType>[]): void
withTypes<MiddlewareConfig extends MiddlewareApiConfig>(): AddMiddleware<
GetState<MiddlewareConfig>,
GetDispatchType<MiddlewareConfig>
>
}
export type WithMiddleware<
State = any,
DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>,
> = BaseActionCreator<
Middleware<any, State, DispatchType>[],
'dynamicMiddleware/add',
{ instanceId: string }
> & {
<Middlewares extends Middleware<any, State, DispatchType>[]>(
...middlewares: Middlewares
): PayloadAction<Middlewares, 'dynamicMiddleware/add', { instanceId: string }>
withTypes<MiddlewareConfig extends MiddlewareApiConfig>(): WithMiddleware<
GetState<MiddlewareConfig>,
GetDispatchType<MiddlewareConfig>
>
}
export interface DynamicDispatch {
// return a version of dispatch that knows about middleware
<Middlewares extends Middleware<any>[]>(
action: PayloadAction<Middlewares, 'dynamicMiddleware/add'>,
): ExtractDispatchExtensions<Middlewares> & this
}
export type MiddlewareEntry<
State = unknown,
DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>,
> = {
middleware: Middleware<any, State, DispatchType>
applied: Map<
MiddlewareAPI<DispatchType, State>,
ReturnType<Middleware<any, State, DispatchType>>
>
}
export type DynamicMiddleware<
State = unknown,
DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>,
> = Middleware<DynamicDispatch, State, DispatchType>
export type DynamicMiddlewareInstance<
State = unknown,
DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>,
> = {
middleware: DynamicMiddleware<State, DispatchType>
addMiddleware: AddMiddleware<State, DispatchType>
withMiddleware: WithMiddleware<State, DispatchType>
instanceId: string
}

View file

@ -0,0 +1,47 @@
import type { EntityAdapter, EntityId, EntityAdapterOptions } from './models'
import { createInitialStateFactory } from './entity_state'
import { createSelectorsFactory } from './state_selectors'
import { createSortedStateAdapter } from './sorted_state_adapter'
import { createUnsortedStateAdapter } from './unsorted_state_adapter'
import type { WithRequiredProp } from '../tsHelpers'
export function createEntityAdapter<T, Id extends EntityId>(
options: WithRequiredProp<EntityAdapterOptions<T, Id>, 'selectId'>,
): EntityAdapter<T, Id>
export function createEntityAdapter<T extends { id: EntityId }>(
options?: Omit<EntityAdapterOptions<T, T['id']>, 'selectId'>,
): EntityAdapter<T, T['id']>
/**
*
* @param options
*
* @public
*/
export function createEntityAdapter<T>(
options: EntityAdapterOptions<T, EntityId> = {},
): EntityAdapter<T, EntityId> {
const {
selectId,
sortComparer,
}: Required<EntityAdapterOptions<T, EntityId>> = {
sortComparer: false,
selectId: (instance: any) => instance.id,
...options,
}
const stateAdapter = sortComparer
? createSortedStateAdapter(selectId, sortComparer)
: createUnsortedStateAdapter(selectId)
const stateFactory = createInitialStateFactory(stateAdapter)
const selectorsFactory = createSelectorsFactory<T, EntityId>()
return {
selectId,
sortComparer,
...stateFactory,
...selectorsFactory,
...stateAdapter,
}
}

View file

@ -0,0 +1,38 @@
import type {
EntityId,
EntityState,
EntityStateAdapter,
EntityStateFactory,
} from './models'
export function getInitialEntityState<T, Id extends EntityId>(): EntityState<
T,
Id
> {
return {
ids: [],
entities: {} as Record<Id, T>,
}
}
export function createInitialStateFactory<T, Id extends EntityId>(
stateAdapter: EntityStateAdapter<T, Id>,
): EntityStateFactory<T, Id> {
function getInitialState(
state?: undefined,
entities?: readonly T[] | Record<Id, T>,
): EntityState<T, Id>
function getInitialState<S extends object>(
additionalState: S,
entities?: readonly T[] | Record<Id, T>,
): EntityState<T, Id> & S
function getInitialState(
additionalState: any = {},
entities?: readonly T[] | Record<Id, T>,
): any {
const state = Object.assign(getInitialEntityState(), additionalState)
return entities ? stateAdapter.setAll(state, entities) : state
}
return { getInitialState }
}

View file

@ -0,0 +1,8 @@
export { createEntityAdapter } from './create_adapter'
export type {
EntityState,
EntityAdapter,
Update,
IdSelector,
Comparer,
} from './models'

View file

@ -0,0 +1,198 @@
import type { Draft } from 'immer'
import type { PayloadAction } from '../createAction'
import type { CastAny, Id } from '../tsHelpers'
import type { UncheckedIndexedAccess } from '../uncheckedindexed.js'
import type { GetSelectorsOptions } from './state_selectors'
/**
* @public
*/
export type EntityId = number | string
/**
* @public
*/
export type Comparer<T> = (a: T, b: T) => number
/**
* @public
*/
export type IdSelector<T, Id extends EntityId> = (model: T) => Id
/**
* @public
*/
export type Update<T, Id extends EntityId> = { id: Id; changes: Partial<T> }
/**
* @public
*/
export interface EntityState<T, Id extends EntityId> {
ids: Id[]
entities: Record<Id, T>
}
/**
* @public
*/
export interface EntityAdapterOptions<T, Id extends EntityId> {
selectId?: IdSelector<T, Id>
sortComparer?: false | Comparer<T>
}
export type PreventAny<S, T, Id extends EntityId> = CastAny<
S,
EntityState<T, Id>
>
export type DraftableEntityState<T, Id extends EntityId> =
| EntityState<T, Id>
| Draft<EntityState<T, Id>>
/**
* @public
*/
export interface EntityStateAdapter<T, Id extends EntityId> {
addOne<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
entity: T,
): S
addOne<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
action: PayloadAction<T>,
): S
addMany<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
entities: readonly T[] | Record<Id, T>,
): S
addMany<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
entities: PayloadAction<readonly T[] | Record<Id, T>>,
): S
setOne<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
entity: T,
): S
setOne<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
action: PayloadAction<T>,
): S
setMany<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
entities: readonly T[] | Record<Id, T>,
): S
setMany<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
entities: PayloadAction<readonly T[] | Record<Id, T>>,
): S
setAll<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
entities: readonly T[] | Record<Id, T>,
): S
setAll<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
entities: PayloadAction<readonly T[] | Record<Id, T>>,
): S
removeOne<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
key: Id,
): S
removeOne<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
key: PayloadAction<Id>,
): S
removeMany<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
keys: readonly Id[],
): S
removeMany<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
keys: PayloadAction<readonly Id[]>,
): S
removeAll<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
): S
updateOne<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
update: Update<T, Id>,
): S
updateOne<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
update: PayloadAction<Update<T, Id>>,
): S
updateMany<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
updates: ReadonlyArray<Update<T, Id>>,
): S
updateMany<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
updates: PayloadAction<ReadonlyArray<Update<T, Id>>>,
): S
upsertOne<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
entity: T,
): S
upsertOne<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
entity: PayloadAction<T>,
): S
upsertMany<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
entities: readonly T[] | Record<Id, T>,
): S
upsertMany<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
entities: PayloadAction<readonly T[] | Record<Id, T>>,
): S
}
/**
* @public
*/
export interface EntitySelectors<T, V, IdType extends EntityId> {
selectIds: (state: V) => IdType[]
selectEntities: (state: V) => Record<IdType, T>
selectAll: (state: V) => T[]
selectTotal: (state: V) => number
selectById: (state: V, id: IdType) => Id<UncheckedIndexedAccess<T>>
}
/**
* @public
*/
export interface EntityStateFactory<T, Id extends EntityId> {
getInitialState(
state?: undefined,
entities?: Record<Id, T> | readonly T[],
): EntityState<T, Id>
getInitialState<S extends object>(
state: S,
entities?: Record<Id, T> | readonly T[],
): EntityState<T, Id> & S
}
/**
* @public
*/
export interface EntityAdapter<T, Id extends EntityId>
extends EntityStateAdapter<T, Id>,
EntityStateFactory<T, Id>,
Required<EntityAdapterOptions<T, Id>> {
getSelectors(
selectState?: undefined,
options?: GetSelectorsOptions,
): EntitySelectors<T, EntityState<T, Id>, Id>
getSelectors<V>(
selectState: (state: V) => EntityState<T, Id>,
options?: GetSelectorsOptions,
): EntitySelectors<T, V, Id>
}

View file

@ -0,0 +1,266 @@
import type {
IdSelector,
Comparer,
EntityStateAdapter,
Update,
EntityId,
DraftableEntityState,
} from './models'
import { createStateOperator } from './state_adapter'
import { createUnsortedStateAdapter } from './unsorted_state_adapter'
import {
selectIdValue,
ensureEntitiesArray,
splitAddedUpdatedEntities,
getCurrent,
} from './utils'
// Borrowed from Replay
export function findInsertIndex<T>(
sortedItems: T[],
item: T,
comparisonFunction: Comparer<T>,
): number {
let lowIndex = 0
let highIndex = sortedItems.length
while (lowIndex < highIndex) {
let middleIndex = (lowIndex + highIndex) >>> 1
const currentItem = sortedItems[middleIndex]
const res = comparisonFunction(item, currentItem)
if (res >= 0) {
lowIndex = middleIndex + 1
} else {
highIndex = middleIndex
}
}
return lowIndex
}
export function insert<T>(
sortedItems: T[],
item: T,
comparisonFunction: Comparer<T>,
): T[] {
const insertAtIndex = findInsertIndex(sortedItems, item, comparisonFunction)
sortedItems.splice(insertAtIndex, 0, item)
return sortedItems
}
export function createSortedStateAdapter<T, Id extends EntityId>(
selectId: IdSelector<T, Id>,
comparer: Comparer<T>,
): EntityStateAdapter<T, Id> {
type R = DraftableEntityState<T, Id>
const { removeOne, removeMany, removeAll } =
createUnsortedStateAdapter(selectId)
function addOneMutably(entity: T, state: R): void {
return addManyMutably([entity], state)
}
function addManyMutably(
newEntities: readonly T[] | Record<Id, T>,
state: R,
existingIds?: Id[],
): void {
newEntities = ensureEntitiesArray(newEntities)
const existingKeys = new Set<Id>(existingIds ?? getCurrent(state.ids))
const addedKeys = new Set<Id>();
const models = newEntities.filter(
(model) => {
const modelId = selectIdValue(model, selectId);
const notAdded = !addedKeys.has(modelId);
if (notAdded) addedKeys.add(modelId);
return !existingKeys.has(modelId) && notAdded;
}
)
if (models.length !== 0) {
mergeFunction(state, models)
}
}
function setOneMutably(entity: T, state: R): void {
return setManyMutably([entity], state)
}
function setManyMutably(
newEntities: readonly T[] | Record<Id, T>,
state: R,
): void {
let deduplicatedEntities = {} as Record<Id, T>;
newEntities = ensureEntitiesArray(newEntities)
if (newEntities.length !== 0) {
for (const item of newEntities) {
const entityId = selectId(item);
// For multiple items with the same ID, we should keep the last one.
deduplicatedEntities[entityId] = item;
delete (state.entities as Record<Id, T>)[entityId]
}
newEntities = ensureEntitiesArray(deduplicatedEntities);
mergeFunction(state, newEntities)
}
}
function setAllMutably(
newEntities: readonly T[] | Record<Id, T>,
state: R,
): void {
newEntities = ensureEntitiesArray(newEntities)
state.entities = {} as Record<Id, T>
state.ids = []
addManyMutably(newEntities, state, [])
}
function updateOneMutably(update: Update<T, Id>, state: R): void {
return updateManyMutably([update], state)
}
function updateManyMutably(
updates: ReadonlyArray<Update<T, Id>>,
state: R,
): void {
let appliedUpdates = false
let replacedIds = false
for (let update of updates) {
const entity: T | undefined = (state.entities as Record<Id, T>)[update.id]
if (!entity) {
continue
}
appliedUpdates = true
Object.assign(entity, update.changes)
const newId = selectId(entity)
if (update.id !== newId) {
// We do support the case where updates can change an item's ID.
// This makes things trickier - go ahead and swap the IDs in state now.
replacedIds = true
delete (state.entities as Record<Id, T>)[update.id]
const oldIndex = (state.ids as Id[]).indexOf(update.id)
state.ids[oldIndex] = newId
;(state.entities as Record<Id, T>)[newId] = entity
}
}
if (appliedUpdates) {
mergeFunction(state, [], appliedUpdates, replacedIds)
}
}
function upsertOneMutably(entity: T, state: R): void {
return upsertManyMutably([entity], state)
}
function upsertManyMutably(
newEntities: readonly T[] | Record<Id, T>,
state: R,
): void {
const [added, updated, existingIdsArray] = splitAddedUpdatedEntities<T, Id>(
newEntities,
selectId,
state,
)
if (added.length) {
addManyMutably(added, state, existingIdsArray)
}
if (updated.length) {
updateManyMutably(updated, state)
}
}
function areArraysEqual(a: readonly unknown[], b: readonly unknown[]) {
if (a.length !== b.length) {
return false
}
for (let i = 0; i < a.length; i++) {
if (a[i] === b[i]) {
continue
}
return false
}
return true
}
type MergeFunction = (
state: R,
addedItems: readonly T[],
appliedUpdates?: boolean,
replacedIds?: boolean,
) => void
const mergeFunction: MergeFunction = (
state,
addedItems,
appliedUpdates,
replacedIds,
) => {
const currentEntities = getCurrent(state.entities)
const currentIds = getCurrent(state.ids)
const stateEntities = state.entities as Record<Id, T>
let ids: Iterable<Id> = currentIds
if (replacedIds) {
ids = new Set(currentIds)
}
let sortedEntities: T[] = []
for (const id of ids) {
const entity = currentEntities[id]
if (entity) {
sortedEntities.push(entity)
}
}
const wasPreviouslyEmpty = sortedEntities.length === 0
// Insert/overwrite all new/updated
for (const item of addedItems) {
stateEntities[selectId(item)] = item
if (!wasPreviouslyEmpty) {
// Binary search insertion generally requires fewer comparisons
insert(sortedEntities, item, comparer)
}
}
if (wasPreviouslyEmpty) {
// All we have is the incoming values, sort them
sortedEntities = addedItems.slice().sort(comparer)
} else if (appliedUpdates) {
// We should have a _mostly_-sorted array already
sortedEntities.sort(comparer)
}
const newSortedIds = sortedEntities.map(selectId)
if (!areArraysEqual(currentIds, newSortedIds)) {
state.ids = newSortedIds
}
}
return {
removeOne,
removeMany,
removeAll,
addOne: createStateOperator(addOneMutably),
updateOne: createStateOperator(updateOneMutably),
upsertOne: createStateOperator(upsertOneMutably),
setOne: createStateOperator(setOneMutably),
setMany: createStateOperator(setManyMutably),
setAll: createStateOperator(setAllMutably),
addMany: createStateOperator(addManyMutably),
updateMany: createStateOperator(updateManyMutably),
upsertMany: createStateOperator(upsertManyMutably),
}
}

View file

@ -0,0 +1,58 @@
import { createNextState, isDraft } from '../immerImports'
import type { Draft } from 'immer'
import type { EntityId, DraftableEntityState, PreventAny } from './models'
import type { PayloadAction } from '../createAction'
import { isFSA } from '../createAction'
export const isDraftTyped = isDraft as <T>(
value: T | Draft<T>,
) => value is Draft<T>
export function createSingleArgumentStateOperator<T, Id extends EntityId>(
mutator: (state: DraftableEntityState<T, Id>) => void,
) {
const operator = createStateOperator(
(_: undefined, state: DraftableEntityState<T, Id>) => mutator(state),
)
return function operation<S extends DraftableEntityState<T, Id>>(
state: PreventAny<S, T, Id>,
): S {
return operator(state as S, undefined)
}
}
export function createStateOperator<T, Id extends EntityId, R>(
mutator: (arg: R, state: DraftableEntityState<T, Id>) => void,
) {
return function operation<S extends DraftableEntityState<T, Id>>(
state: S,
arg: R | PayloadAction<R>,
): S {
function isPayloadActionArgument(
arg: R | PayloadAction<R>,
): arg is PayloadAction<R> {
return isFSA(arg)
}
const runMutator = (draft: DraftableEntityState<T, Id>) => {
if (isPayloadActionArgument(arg)) {
mutator(arg.payload, draft)
} else {
mutator(arg, draft)
}
}
if (isDraftTyped<DraftableEntityState<T, Id>>(state)) {
// we must already be inside a `createNextState` call, likely because
// this is being wrapped in `createReducer` or `createSlice`.
// It's safe to just pass the draft to the mutator.
runMutator(state)
// since it's a draft, we'll just return it
return state
}
return createNextState(state, runMutator)
}
}

View file

@ -0,0 +1,73 @@
import type { CreateSelectorFunction, Selector } from 'reselect'
import { createDraftSafeSelector } from '../createDraftSafeSelector'
import type { EntityId, EntitySelectors, EntityState } from './models'
type AnyCreateSelectorFunction = CreateSelectorFunction<any, any, any>
export type GetSelectorsOptions = {
createSelector?: AnyCreateSelectorFunction
}
export function createSelectorsFactory<T, Id extends EntityId>() {
function getSelectors(
selectState?: undefined,
options?: GetSelectorsOptions,
): EntitySelectors<T, EntityState<T, Id>, Id>
function getSelectors<V>(
selectState: (state: V) => EntityState<T, Id>,
options?: GetSelectorsOptions,
): EntitySelectors<T, V, Id>
function getSelectors<V>(
selectState?: (state: V) => EntityState<T, Id>,
options: GetSelectorsOptions = {},
): EntitySelectors<T, any, Id> {
const {
createSelector = createDraftSafeSelector as AnyCreateSelectorFunction,
} = options
const selectIds = (state: EntityState<T, Id>) => state.ids
const selectEntities = (state: EntityState<T, Id>) => state.entities
const selectAll = createSelector(
selectIds,
selectEntities,
(ids, entities): T[] => ids.map((id) => entities[id]!),
)
const selectId = (_: unknown, id: Id) => id
const selectById = (entities: Record<Id, T>, id: Id) => entities[id]
const selectTotal = createSelector(selectIds, (ids) => ids.length)
if (!selectState) {
return {
selectIds,
selectEntities,
selectAll,
selectTotal,
selectById: createSelector(selectEntities, selectId, selectById),
}
}
const selectGlobalizedEntities = createSelector(
selectState as Selector<V, EntityState<T, Id>>,
selectEntities,
)
return {
selectIds: createSelector(selectState, selectIds),
selectEntities: selectGlobalizedEntities,
selectAll: createSelector(selectState, selectAll),
selectTotal: createSelector(selectState, selectTotal),
selectById: createSelector(
selectGlobalizedEntities,
selectId,
selectById,
),
}
}
return { getSelectors }
}

View file

@ -0,0 +1,204 @@
import type { Draft } from 'immer'
import type {
EntityStateAdapter,
IdSelector,
Update,
EntityId,
DraftableEntityState,
} from './models'
import {
createStateOperator,
createSingleArgumentStateOperator,
} from './state_adapter'
import {
selectIdValue,
ensureEntitiesArray,
splitAddedUpdatedEntities,
} from './utils'
export function createUnsortedStateAdapter<T, Id extends EntityId>(
selectId: IdSelector<T, Id>,
): EntityStateAdapter<T, Id> {
type R = DraftableEntityState<T, Id>
function addOneMutably(entity: T, state: R): void {
const key = selectIdValue(entity, selectId)
if (key in state.entities) {
return
}
state.ids.push(key as Id & Draft<Id>)
;(state.entities as Record<Id, T>)[key] = entity
}
function addManyMutably(
newEntities: readonly T[] | Record<Id, T>,
state: R,
): void {
newEntities = ensureEntitiesArray(newEntities)
for (const entity of newEntities) {
addOneMutably(entity, state)
}
}
function setOneMutably(entity: T, state: R): void {
const key = selectIdValue(entity, selectId)
if (!(key in state.entities)) {
state.ids.push(key as Id & Draft<Id>)
}
;(state.entities as Record<Id, T>)[key] = entity
}
function setManyMutably(
newEntities: readonly T[] | Record<Id, T>,
state: R,
): void {
newEntities = ensureEntitiesArray(newEntities)
for (const entity of newEntities) {
setOneMutably(entity, state)
}
}
function setAllMutably(
newEntities: readonly T[] | Record<Id, T>,
state: R,
): void {
newEntities = ensureEntitiesArray(newEntities)
state.ids = []
state.entities = {} as Record<Id, T>
addManyMutably(newEntities, state)
}
function removeOneMutably(key: Id, state: R): void {
return removeManyMutably([key], state)
}
function removeManyMutably(keys: readonly Id[], state: R): void {
let didMutate = false
keys.forEach((key) => {
if (key in state.entities) {
delete (state.entities as Record<Id, T>)[key]
didMutate = true
}
})
if (didMutate) {
state.ids = (state.ids as Id[]).filter((id) => id in state.entities) as
| Id[]
| Draft<Id[]>
}
}
function removeAllMutably(state: R): void {
Object.assign(state, {
ids: [],
entities: {},
})
}
function takeNewKey(
keys: { [id: string]: Id },
update: Update<T, Id>,
state: R,
): boolean {
const original: T | undefined = (state.entities as Record<Id, T>)[update.id]
if (original === undefined) {
return false
}
const updated: T = Object.assign({}, original, update.changes)
const newKey = selectIdValue(updated, selectId)
const hasNewKey = newKey !== update.id
if (hasNewKey) {
keys[update.id] = newKey
delete (state.entities as Record<Id, T>)[update.id]
}
;(state.entities as Record<Id, T>)[newKey] = updated
return hasNewKey
}
function updateOneMutably(update: Update<T, Id>, state: R): void {
return updateManyMutably([update], state)
}
function updateManyMutably(
updates: ReadonlyArray<Update<T, Id>>,
state: R,
): void {
const newKeys: { [id: string]: Id } = {}
const updatesPerEntity: { [id: string]: Update<T, Id> } = {}
updates.forEach((update) => {
// Only apply updates to entities that currently exist
if (update.id in state.entities) {
// If there are multiple updates to one entity, merge them together
updatesPerEntity[update.id] = {
id: update.id,
// Spreads ignore falsy values, so this works even if there isn't
// an existing update already at this key
changes: {
...updatesPerEntity[update.id]?.changes,
...update.changes,
},
}
}
})
updates = Object.values(updatesPerEntity)
const didMutateEntities = updates.length > 0
if (didMutateEntities) {
const didMutateIds =
updates.filter((update) => takeNewKey(newKeys, update, state)).length >
0
if (didMutateIds) {
state.ids = Object.values(state.entities).map((e) =>
selectIdValue(e as T, selectId),
)
}
}
}
function upsertOneMutably(entity: T, state: R): void {
return upsertManyMutably([entity], state)
}
function upsertManyMutably(
newEntities: readonly T[] | Record<Id, T>,
state: R,
): void {
const [added, updated] = splitAddedUpdatedEntities<T, Id>(
newEntities,
selectId,
state,
)
addManyMutably(added, state)
updateManyMutably(updated, state)
}
return {
removeAll: createSingleArgumentStateOperator(removeAllMutably),
addOne: createStateOperator(addOneMutably),
addMany: createStateOperator(addManyMutably),
setOne: createStateOperator(setOneMutably),
setMany: createStateOperator(setManyMutably),
setAll: createStateOperator(setAllMutably),
updateOne: createStateOperator(updateOneMutably),
updateMany: createStateOperator(updateManyMutably),
upsertOne: createStateOperator(upsertOneMutably),
upsertMany: createStateOperator(upsertManyMutably),
removeOne: createStateOperator(removeOneMutably),
removeMany: createStateOperator(removeManyMutably),
}
}

View file

@ -0,0 +1,68 @@
import type { Draft } from 'immer'
import { current, isDraft } from '../immerImports'
import type {
DraftableEntityState,
EntityId,
IdSelector,
Update,
} from './models'
export function selectIdValue<T, Id extends EntityId>(
entity: T,
selectId: IdSelector<T, Id>,
) {
const key = selectId(entity)
if (process.env.NODE_ENV !== 'production' && key === undefined) {
console.warn(
'The entity passed to the `selectId` implementation returned undefined.',
'You should probably provide your own `selectId` implementation.',
'The entity that was passed:',
entity,
'The `selectId` implementation:',
selectId.toString(),
)
}
return key
}
export function ensureEntitiesArray<T, Id extends EntityId>(
entities: readonly T[] | Record<Id, T>,
): readonly T[] {
if (!Array.isArray(entities)) {
entities = Object.values(entities)
}
return entities
}
export function getCurrent<T>(value: T | Draft<T>): T {
return (isDraft(value) ? current(value) : value) as T
}
export function splitAddedUpdatedEntities<T, Id extends EntityId>(
newEntities: readonly T[] | Record<Id, T>,
selectId: IdSelector<T, Id>,
state: DraftableEntityState<T, Id>,
): [T[], Update<T, Id>[], Id[]] {
newEntities = ensureEntitiesArray(newEntities)
const existingIdsArray = getCurrent(state.ids)
const existingIds = new Set<Id>(existingIdsArray)
const added: T[] = []
const addedIds = new Set<Id>([])
const updated: Update<T, Id>[] = []
for (const entity of newEntities) {
const id = selectIdValue(entity, selectId)
if (existingIds.has(id) || addedIds.has(id)) {
updated.push({ id, changes: entity })
} else {
addedIds.add(id)
added.push(entity)
}
}
return [added, updated, existingIdsArray]
}

View file

@ -0,0 +1,13 @@
/**
* Adapted from React: https://github.com/facebook/react/blob/master/packages/shared/formatProdErrorMessage.js
*
* Do not require this module directly! Use normal throw error calls. These messages will be replaced with error codes
* during build.
* @param {number} code
*/
export function formatProdErrorMessage(code: number) {
return (
`Minified Redux Toolkit error #${code}; visit https://redux-toolkit.js.org/Errors?code=${code} for the full message or ` +
'use the non-minified dev environment for full errors. '
)
}

View file

@ -0,0 +1,31 @@
import type { StoreEnhancer } from 'redux'
import type { AutoBatchOptions } from './autoBatchEnhancer'
import { autoBatchEnhancer } from './autoBatchEnhancer'
import { Tuple } from './utils'
import type { Middlewares } from './configureStore'
import type { ExtractDispatchExtensions } from './tsHelpers'
type GetDefaultEnhancersOptions = {
autoBatch?: boolean | AutoBatchOptions
}
export type GetDefaultEnhancers<M extends Middlewares<any>> = (
options?: GetDefaultEnhancersOptions,
) => Tuple<[StoreEnhancer<{ dispatch: ExtractDispatchExtensions<M> }>]>
export const buildGetDefaultEnhancers = <M extends Middlewares<any>>(
middlewareEnhancer: StoreEnhancer<{ dispatch: ExtractDispatchExtensions<M> }>,
): GetDefaultEnhancers<M> =>
function getDefaultEnhancers(options) {
const { autoBatch = true } = options ?? {}
let enhancerArray = new Tuple<StoreEnhancer[]>(middlewareEnhancer)
if (autoBatch) {
enhancerArray.push(
autoBatchEnhancer(
typeof autoBatch === 'object' ? autoBatch : undefined,
),
)
}
return enhancerArray as any
}

View file

@ -0,0 +1,113 @@
import type { Middleware, UnknownAction } from 'redux'
import type { ThunkMiddleware } from 'redux-thunk'
import { thunk as thunkMiddleware, withExtraArgument } from 'redux-thunk'
import type { ActionCreatorInvariantMiddlewareOptions } from './actionCreatorInvariantMiddleware'
import { createActionCreatorInvariantMiddleware } from './actionCreatorInvariantMiddleware'
import type { ImmutableStateInvariantMiddlewareOptions } from './immutableStateInvariantMiddleware'
/* PROD_START_REMOVE_UMD */
import { createImmutableStateInvariantMiddleware } from './immutableStateInvariantMiddleware'
/* PROD_STOP_REMOVE_UMD */
import type { SerializableStateInvariantMiddlewareOptions } from './serializableStateInvariantMiddleware'
import { createSerializableStateInvariantMiddleware } from './serializableStateInvariantMiddleware'
import type { ExcludeFromTuple } from './tsHelpers'
import { Tuple } from './utils'
function isBoolean(x: any): x is boolean {
return typeof x === 'boolean'
}
interface ThunkOptions<E = any> {
extraArgument: E
}
interface GetDefaultMiddlewareOptions {
thunk?: boolean | ThunkOptions
immutableCheck?: boolean | ImmutableStateInvariantMiddlewareOptions
serializableCheck?: boolean | SerializableStateInvariantMiddlewareOptions
actionCreatorCheck?: boolean | ActionCreatorInvariantMiddlewareOptions
}
export type ThunkMiddlewareFor<
S,
O extends GetDefaultMiddlewareOptions = {},
> = O extends {
thunk: false
}
? never
: O extends { thunk: { extraArgument: infer E } }
? ThunkMiddleware<S, UnknownAction, E>
: ThunkMiddleware<S, UnknownAction>
export type GetDefaultMiddleware<S = any> = <
O extends GetDefaultMiddlewareOptions = {
thunk: true
immutableCheck: true
serializableCheck: true
actionCreatorCheck: true
},
>(
options?: O,
) => Tuple<ExcludeFromTuple<[ThunkMiddlewareFor<S, O>], never>>
export const buildGetDefaultMiddleware = <S = any>(): GetDefaultMiddleware<S> =>
function getDefaultMiddleware(options) {
const {
thunk = true,
immutableCheck = true,
serializableCheck = true,
actionCreatorCheck = true,
} = options ?? {}
let middlewareArray = new Tuple<Middleware[]>()
if (thunk) {
if (isBoolean(thunk)) {
middlewareArray.push(thunkMiddleware)
} else {
middlewareArray.push(withExtraArgument(thunk.extraArgument))
}
}
if (process.env.NODE_ENV !== 'production') {
if (immutableCheck) {
/* PROD_START_REMOVE_UMD */
let immutableOptions: ImmutableStateInvariantMiddlewareOptions = {}
if (!isBoolean(immutableCheck)) {
immutableOptions = immutableCheck
}
middlewareArray.unshift(
createImmutableStateInvariantMiddleware(immutableOptions),
)
/* PROD_STOP_REMOVE_UMD */
}
if (serializableCheck) {
let serializableOptions: SerializableStateInvariantMiddlewareOptions =
{}
if (!isBoolean(serializableCheck)) {
serializableOptions = serializableCheck
}
middlewareArray.push(
createSerializableStateInvariantMiddleware(serializableOptions),
)
}
if (actionCreatorCheck) {
let actionCreatorOptions: ActionCreatorInvariantMiddlewareOptions = {}
if (!isBoolean(actionCreatorCheck)) {
actionCreatorOptions = actionCreatorCheck
}
middlewareArray.unshift(
createActionCreatorInvariantMiddleware(actionCreatorOptions),
)
}
}
return middlewareArray as any
}

View file

@ -0,0 +1,7 @@
export {
current,
isDraft,
produce as createNextState,
isDraftable,
setUseStrictIteration,
} from 'immer'

View file

@ -0,0 +1,274 @@
import type { Middleware } from 'redux'
import type { IgnoredPaths } from './serializableStateInvariantMiddleware'
import { getTimeMeasureUtils } from './utils'
type EntryProcessor = (key: string, value: any) => any
/**
* The default `isImmutable` function.
*
* @public
*/
export function isImmutableDefault(value: unknown): boolean {
return typeof value !== 'object' || value == null || Object.isFrozen(value)
}
export function trackForMutations(
isImmutable: IsImmutableFunc,
ignoredPaths: IgnoredPaths | undefined,
obj: any,
) {
const trackedProperties = trackProperties(isImmutable, ignoredPaths, obj)
return {
detectMutations() {
return detectMutations(isImmutable, ignoredPaths, trackedProperties, obj)
},
}
}
interface TrackedProperty {
value: any
children: Record<string, any>
}
function trackProperties(
isImmutable: IsImmutableFunc,
ignoredPaths: IgnoredPaths = [],
obj: Record<string, any>,
path: string = '',
checkedObjects: Set<Record<string, any>> = new Set(),
) {
const tracked: Partial<TrackedProperty> = { value: obj }
if (!isImmutable(obj) && !checkedObjects.has(obj)) {
checkedObjects.add(obj)
tracked.children = {}
const hasIgnoredPaths = ignoredPaths.length > 0
for (const key in obj) {
const nestedPath = path ? path + '.' + key : key
if (hasIgnoredPaths) {
const hasMatches = ignoredPaths.some((ignored) => {
if (ignored instanceof RegExp) {
return ignored.test(nestedPath)
}
return nestedPath === ignored
})
if (hasMatches) {
continue
}
}
tracked.children[key] = trackProperties(
isImmutable,
ignoredPaths,
obj[key],
nestedPath,
)
}
}
return tracked as TrackedProperty
}
function detectMutations(
isImmutable: IsImmutableFunc,
ignoredPaths: IgnoredPaths = [],
trackedProperty: TrackedProperty,
obj: any,
sameParentRef: boolean = false,
path: string = '',
): { wasMutated: boolean; path?: string } {
const prevObj = trackedProperty ? trackedProperty.value : undefined
const sameRef = prevObj === obj
if (sameParentRef && !sameRef && !Number.isNaN(obj)) {
return { wasMutated: true, path }
}
if (isImmutable(prevObj) || isImmutable(obj)) {
return { wasMutated: false }
}
// Gather all keys from prev (tracked) and after objs
const keysToDetect: Record<string, boolean> = {}
for (let key in trackedProperty.children) {
keysToDetect[key] = true
}
for (let key in obj) {
keysToDetect[key] = true
}
const hasIgnoredPaths = ignoredPaths.length > 0
for (let key in keysToDetect) {
const nestedPath = path ? path + '.' + key : key
if (hasIgnoredPaths) {
const hasMatches = ignoredPaths.some((ignored) => {
if (ignored instanceof RegExp) {
return ignored.test(nestedPath)
}
return nestedPath === ignored
})
if (hasMatches) {
continue
}
}
const result = detectMutations(
isImmutable,
ignoredPaths,
trackedProperty.children[key],
obj[key],
sameRef,
nestedPath,
)
if (result.wasMutated) {
return result
}
}
return { wasMutated: false }
}
type IsImmutableFunc = (value: any) => boolean
/**
* Options for `createImmutableStateInvariantMiddleware()`.
*
* @public
*/
export interface ImmutableStateInvariantMiddlewareOptions {
/**
Callback function to check if a value is considered to be immutable.
This function is applied recursively to every value contained in the state.
The default implementation will return true for primitive types
(like numbers, strings, booleans, null and undefined).
*/
isImmutable?: IsImmutableFunc
/**
An array of dot-separated path strings that match named nodes from
the root state to ignore when checking for immutability.
Defaults to undefined
*/
ignoredPaths?: IgnoredPaths
/** Print a warning if checks take longer than N ms. Default: 32ms */
warnAfter?: number
}
/**
* Creates a middleware that checks whether any state was mutated in between
* dispatches or during a dispatch. If any mutations are detected, an error is
* thrown.
*
* @param options Middleware options.
*
* @public
*/
export function createImmutableStateInvariantMiddleware(
options: ImmutableStateInvariantMiddlewareOptions = {},
): Middleware {
if (process.env.NODE_ENV === 'production') {
return () => (next) => (action) => next(action)
} else {
function stringify(
obj: any,
serializer?: EntryProcessor,
indent?: string | number,
decycler?: EntryProcessor,
): string {
return JSON.stringify(obj, getSerialize(serializer, decycler), indent)
}
function getSerialize(
serializer?: EntryProcessor,
decycler?: EntryProcessor,
): EntryProcessor {
let stack: any[] = [],
keys: any[] = []
if (!decycler)
decycler = function (_: string, value: any) {
if (stack[0] === value) return '[Circular ~]'
return (
'[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']'
)
}
return function (this: any, key: string, value: any) {
if (stack.length > 0) {
var thisPos = stack.indexOf(this)
~thisPos ? stack.splice(thisPos + 1) : stack.push(this)
~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key)
if (~stack.indexOf(value)) value = decycler!.call(this, key, value)
} else stack.push(value)
return serializer == null ? value : serializer.call(this, key, value)
}
}
let {
isImmutable = isImmutableDefault,
ignoredPaths,
warnAfter = 32,
} = options
const track = trackForMutations.bind(null, isImmutable, ignoredPaths)
return ({ getState }) => {
let state = getState()
let tracker = track(state)
let result
return (next) => (action) => {
const measureUtils = getTimeMeasureUtils(
warnAfter,
'ImmutableStateInvariantMiddleware',
)
measureUtils.measureTime(() => {
state = getState()
result = tracker.detectMutations()
// Track before potentially not meeting the invariant
tracker = track(state)
if (result.wasMutated) {
throw new Error(
`A state mutation was detected between dispatches, in the path '${
result.path || ''
}'. This may cause incorrect behavior. (https://redux.js.org/style-guide/style-guide#do-not-mutate-state)`,
)
}
})
const dispatchedAction = next(action)
measureUtils.measureTime(() => {
state = getState()
result = tracker.detectMutations()
// Track before potentially not meeting the invariant
tracker = track(state)
if (result.wasMutated) {
throw new Error(
`A state mutation was detected inside a dispatch, in the path: ${
result.path || ''
}. Take a look at the reducer(s) handling the action ${stringify(
action,
)}. (https://redux.js.org/style-guide/style-guide#do-not-mutate-state)`,
)
}
})
measureUtils.warnIfExceeded()
return dispatchedAction
}
}
}
}

213
frontend/node_modules/@reduxjs/toolkit/src/index.ts generated vendored Normal file
View file

@ -0,0 +1,213 @@
// This must remain here so that the `mangleErrors.cjs` build script
// does not have to import this into each source file it rewrites.
import { formatProdErrorMessage } from './formatProdErrorMessage'
export * from 'redux'
export { freeze, original } from 'immer'
export { createNextState, current, isDraft } from './immerImports'
export type { Draft, WritableDraft } from 'immer'
export { createSelector, lruMemoize } from 'reselect'
export { createSelectorCreator, weakMapMemoize } from './reselectImports'
export type { Selector, OutputSelector } from 'reselect'
export {
createDraftSafeSelector,
createDraftSafeSelectorCreator,
} from './createDraftSafeSelector'
export type { ThunkAction, ThunkDispatch, ThunkMiddleware } from 'redux-thunk'
export {
// js
configureStore,
} from './configureStore'
export type {
// types
ConfigureStoreOptions,
EnhancedStore,
} from './configureStore'
export type { DevToolsEnhancerOptions } from './devtoolsExtension'
export {
// js
createAction,
isActionCreator,
isFSA as isFluxStandardAction,
} from './createAction'
export type {
// types
PayloadAction,
PayloadActionCreator,
ActionCreatorWithNonInferrablePayload,
ActionCreatorWithOptionalPayload,
ActionCreatorWithPayload,
ActionCreatorWithoutPayload,
ActionCreatorWithPreparedPayload,
PrepareAction,
} from './createAction'
export {
// js
createReducer,
} from './createReducer'
export type {
// types
Actions,
CaseReducer,
CaseReducers,
} from './createReducer'
export {
// js
createSlice,
buildCreateSlice,
asyncThunkCreator,
ReducerType,
} from './createSlice'
export type {
// types
CreateSliceOptions,
Slice,
CaseReducerActions,
SliceCaseReducers,
ValidateSliceCaseReducers,
CaseReducerWithPrepare,
ReducerCreators,
SliceSelectors,
} from './createSlice'
export type { ActionCreatorInvariantMiddlewareOptions } from './actionCreatorInvariantMiddleware'
export { createActionCreatorInvariantMiddleware } from './actionCreatorInvariantMiddleware'
export {
// js
createImmutableStateInvariantMiddleware,
isImmutableDefault,
} from './immutableStateInvariantMiddleware'
export type {
// types
ImmutableStateInvariantMiddlewareOptions,
} from './immutableStateInvariantMiddleware'
export {
// js
createSerializableStateInvariantMiddleware,
findNonSerializableValue,
isPlain,
} from './serializableStateInvariantMiddleware'
export type {
// types
SerializableStateInvariantMiddlewareOptions,
} from './serializableStateInvariantMiddleware'
export type {
// types
ActionReducerMapBuilder,
AsyncThunkReducers,
} from './mapBuilders'
export { Tuple } from './utils'
export { createEntityAdapter } from './entities/create_adapter'
export type {
EntityState,
EntityAdapter,
EntitySelectors,
EntityStateAdapter,
EntityId,
Update,
IdSelector,
Comparer,
} from './entities/models'
export {
createAsyncThunk,
unwrapResult,
miniSerializeError,
} from './createAsyncThunk'
export type {
AsyncThunk,
AsyncThunkConfig,
AsyncThunkDispatchConfig,
AsyncThunkOptions,
AsyncThunkAction,
AsyncThunkPayloadCreatorReturnValue,
AsyncThunkPayloadCreator,
GetState,
GetThunkAPI,
SerializedError,
CreateAsyncThunkFunction,
} from './createAsyncThunk'
export {
// js
isAllOf,
isAnyOf,
isPending,
isRejected,
isFulfilled,
isAsyncThunkAction,
isRejectedWithValue,
} from './matchers'
export type {
// types
ActionMatchingAllOf,
ActionMatchingAnyOf,
} from './matchers'
export { nanoid } from './nanoid'
export type {
ListenerEffect,
ListenerMiddleware,
ListenerEffectAPI,
ListenerMiddlewareInstance,
CreateListenerMiddlewareOptions,
ListenerErrorHandler,
TypedStartListening,
TypedAddListener,
TypedStopListening,
TypedRemoveListener,
UnsubscribeListener,
UnsubscribeListenerOptions,
ForkedTaskExecutor,
ForkedTask,
ForkedTaskAPI,
AsyncTaskExecutor,
SyncTaskExecutor,
TaskCancelled,
TaskRejected,
TaskResolved,
TaskResult,
} from './listenerMiddleware/index'
export type { AnyListenerPredicate } from './listenerMiddleware/types'
export {
createListenerMiddleware,
addListener,
removeListener,
clearAllListeners,
TaskAbortError,
} from './listenerMiddleware/index'
export type {
AddMiddleware,
DynamicDispatch,
DynamicMiddlewareInstance,
GetDispatchType as GetDispatch,
MiddlewareApiConfig,
} from './dynamicMiddleware/types'
export { createDynamicMiddleware } from './dynamicMiddleware/index'
export {
SHOULD_AUTOBATCH,
prepareAutoBatched,
autoBatchEnhancer,
} from './autoBatchEnhancer'
export type { AutoBatchOptions } from './autoBatchEnhancer'
export { combineSlices } from './combineSlices'
export type {
CombinedSliceReducer,
WithSlice,
WithSlicePreloadedState,
} from './combineSlices'
export type {
ExtractDispatchExtensions as TSHelpersExtractDispatchExtensions,
SafePromise,
} from './tsHelpers'
export { formatProdErrorMessage } from './formatProdErrorMessage'

Some files were not shown because too many files have changed in this diff Show more