From 5b4510e37d5b60c0faec17cdd91b0092849fd9a6 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 17:27:48 -0500 Subject: [PATCH 01/16] Add errors page to sidebar --- website/sidebars.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/sidebars.js b/website/sidebars.js index e1fc4fd2c0..a83c4dfebd 100755 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -141,7 +141,8 @@ module.exports = { 'api/combinereducers', 'api/applymiddleware', 'api/bindactioncreators', - 'api/compose' + 'api/compose', + { type: 'link', label: 'Error Messages', href: '/errors' } ], 'Redux Toolkit': ['redux-toolkit/overview'] } From 9405381c53dfd4da6062052b9e6e14f0a7ed76d2 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 17:28:05 -0500 Subject: [PATCH 02/16] Remove url-params-polyfill dep --- website/package.json | 3 +-- website/yarn.lock | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/website/package.json b/website/package.json index 495b88e57a..3e836e1fbb 100755 --- a/website/package.json +++ b/website/package.json @@ -14,8 +14,7 @@ "react-dom": "^17.0.2", "react-lite-youtube-embed": "^2.0.3", "remark-typescript-tools": "^1.0.11", - "typescript": "~4.7", - "url-search-params-polyfill": "^8.1.0" + "typescript": "~4.7" }, "devDependencies": { "@reduxjs/toolkit": "^1.8.2", diff --git a/website/yarn.lock b/website/yarn.lock index de52353219..5d8085bf61 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -11726,7 +11726,6 @@ __metadata: react-redux: ^8.0.2 remark-typescript-tools: ^1.0.11 typescript: ~4.7 - url-search-params-polyfill: ^8.1.0 languageName: unknown linkType: soft @@ -13125,13 +13124,6 @@ __metadata: languageName: node linkType: hard -"url-search-params-polyfill@npm:^8.1.0": - version: 8.2.5 - resolution: "url-search-params-polyfill@npm:8.2.5" - checksum: 6791c3e2d62d415a6ca7d12c0c679c8621e918726ea82407a3cb9fd149060bdf2c17193a7623ac983551eeba34f987e465822f69364d1fd442e7983aea4d568e - languageName: node - linkType: hard - "use-composed-ref@npm:^1.3.0": version: 1.3.0 resolution: "use-composed-ref@npm:1.3.0" From 17cb692768d169bdcabe1fe8016f5ea93e428ccd Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 17:28:14 -0500 Subject: [PATCH 03/16] Remove url-params-polyfill usage --- website/src/pages/errors.js | 1 - 1 file changed, 1 deletion(-) diff --git a/website/src/pages/errors.js b/website/src/pages/errors.js index 4a9fe31369..3dda026207 100644 --- a/website/src/pages/errors.js +++ b/website/src/pages/errors.js @@ -4,7 +4,6 @@ import { useLocation } from '@docusaurus/router' import useDocusaurusContext from '@docusaurus/useDocusaurusContext' import styles from './styles.module.css' import errorCodes from '../../../errors.json' -import 'url-search-params-polyfill' function Errors() { const location = useLocation() From 905b94ed74f4b7ac2760cddc55e142a5f2c7e2f8 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 17:33:22 -0500 Subject: [PATCH 04/16] Update React docs links --- README.md | 4 ++-- docs/usage/WritingTests.mdx | 12 +++++++----- docs/usage/side-effects-approaches.mdx | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b79c3e657b..ae0ef360e5 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ Redux is a predictable state container for JavaScript apps. It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. On top of that, it provides a great developer experience, such as [live code editing combined with a time traveling debugger](https://github.com/reduxjs/redux-devtools). -You can use Redux together with [React](https://reactjs.org), or with any other view library. -It is tiny (2kB, including dependencies), and has a rich ecosystem of addons. +You can use Redux together with [React](https://react.dev), or with any other view library. +The Redux core tiny (2kB, including dependencies), and has a rich ecosystem of addons. ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/reduxjs/redux/test.yaml?branch=master&event=push&style=flat-square) [![npm version](https://img.shields.io/npm/v/redux.svg?style=flat-square)](https://www.npmjs.com/package/redux) diff --git a/docs/usage/WritingTests.mdx b/docs/usage/WritingTests.mdx index 409980b3c2..afd0ed81e5 100644 --- a/docs/usage/WritingTests.mdx +++ b/docs/usage/WritingTests.mdx @@ -164,6 +164,7 @@ export type RootState = ReturnType export type AppStore = ReturnType export type AppDispatch = AppStore['dispatch'] ``` + ```ts title="app/hooks.ts" // file: features/users/userSlice.ts noEmit import { createSlice } from '@reduxjs/toolkit' @@ -366,7 +367,7 @@ interface ExtendedRenderOptions extends Omit { export function renderWithProviders( ui: React.ReactElement, { - preloadedState= {}, + preloadedState = {}, // Automatically create a store instance if no store was passed in store = configureStore({ reducer: { user: userReducer }, preloadedState }), ...renderOptions @@ -593,8 +594,8 @@ import UserDisplay from '../UserDisplay' // when receiving a get request to the `/api/user` endpoint export const handlers = [ http.get('/api/user', async () => { - await delay(150) - return HttpResponse.json('John Smith') + await delay(150) + return HttpResponse.json('John Smith') }) ] @@ -754,7 +755,9 @@ test('should handle a todo being added to an empty list', () => { }) test('should handle a todo being added to an existing list', () => { - const previousState: Todo[] = [{ text: 'Run the tests', completed: true, id: 0 }] + const previousState: Todo[] = [ + { text: 'Run the tests', completed: true, id: 0 } + ] expect(reducer(previousState, todoAdded('Use Redux'))).toEqual([ { text: 'Run the tests', completed: true, id: 0 }, @@ -859,6 +862,5 @@ In some cases, you will need to modify the `create` function to use different mo ## Further Information - [React Testing Library](https://testing-library.com/docs/react-testing-library/intro): React Testing Library is a very light-weight solution for testing React components. It provides light utility functions on top of react-dom and react-dom/test-utils, in a way that encourages better testing practices. Its primary guiding principle is: "The more your tests resemble the way your software is used, the more confidence they can give you." -- [React Test Utils](https://reactjs.org/docs/test-utils.html): ReactTestUtils makes it easy to test React components in the testing framework of your choice. React Testing Library uses the `act` function exported by React Test Utils. - [Blogged Answers: The Evolution of Redux Testing Approaches](https://blog.isquaredsoftware.com/2021/06/the-evolution-of-redux-testing-approaches/): Mark Erikson's thoughts on how Redux testing has evolved from 'isolation' to 'integration'. - [Testing Implementation Details](https://kentcdodds.com/blog/testing-implementation-details): Blog post by Kent C. Dodds on why he recommends to avoid testing implementation details. diff --git a/docs/usage/side-effects-approaches.mdx b/docs/usage/side-effects-approaches.mdx index 6725bd2fd1..7964cbb469 100644 --- a/docs/usage/side-effects-approaches.mdx +++ b/docs/usage/side-effects-approaches.mdx @@ -73,7 +73,7 @@ We recommend using the tools that best fit each use case (see below for the reas ### Why RTK Query for Data Fetching -Per [the React docs section on "alternatives for data fetching in Effects"](https://beta.reactjs.org/learn/synchronizing-with-effects#what-are-good-alternatives-to-data-fetching-in-effects), you _should_ use either data fetching approaches that are built into a server-side framework, or a client-side cache. **You should _not_ write data fetching and cache management code yourself.** +Per [the React docs section on "alternatives for data fetching in Effects"](https://react.dev/learn/synchronizing-with-effects#what-are-good-alternatives-to-data-fetching-in-effects), you _should_ use either data fetching approaches that are built into a server-side framework, or a client-side cache. **You should _not_ write data fetching and cache management code yourself.** RTK Query was specifically designed to be a complete data fetching and caching layer for Redux-based applications. It manages all the fetching, caching, and loading status logic for you, and covers many edge cases that are typically forgotten or hard to handle if you write data fetching code yourself, as well as having cache lifecycle management built-in. It also makes it simple to fetch and use data via the auto-generated React hooks. From 0ccd57d2ad78f3a2944f6cb10b6f083d8a227022 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 17:44:52 -0500 Subject: [PATCH 05/16] Paste RTK 2.0 migration guide --- docs/usage/migrations/migrating-rtk-2.md | 852 +++++++++++++++++++++++ website/sidebars.js | 10 +- 2 files changed, 861 insertions(+), 1 deletion(-) create mode 100644 docs/usage/migrations/migrating-rtk-2.md diff --git a/docs/usage/migrations/migrating-rtk-2.md b/docs/usage/migrations/migrating-rtk-2.md new file mode 100644 index 0000000000..fea1b5f593 --- /dev/null +++ b/docs/usage/migrations/migrating-rtk-2.md @@ -0,0 +1,852 @@ +--- +id: migrating-rtk-2 +title: Migrating to RTK 2.x and Redux 5.x +sidebar_label: Migrating to RTK 2.x and Redux 5.x +hide_title: true +toc_max_heading_level: 4 +--- + +  + +
+ +# Migrating to RTK 2.x and Redux 5.x + +:::tip What You'll Learn + +- What's changed in Redux Toolkit 2.0 and Redux core 5.0, including breaking changes and new features + +::: + +## Introduction + +Redux Toolkit has been available since 2019, and today it's the standard way to write Redux apps. We've gone 4+ years without any breaking changes. Now, RTK 2.0 gives us a chance to modernize the packaging, clean up deprecated options, and tighten up some edge cases. + +Redux Toolkit 2.0 is accompanied by major versions of all the other Redux packages: Redux core 5.0, React-Redux 9.0, Redux Thunk 3.0, and Reselect 5.0. + +This page lists known potentially breaking changes in Redux Toolkit 2.0 and Redux core 5.0, as well as new features in Redux Toolkit 2.0. As a reminder, **you should not need to actually install or use the core `redux` package directly** - RTK wraps that, and re-exports all methods and types. + +In practice, **most of the "breaking" changes should not have an actual effect on end users, and we expect that many projects can just update the package versions with very few code changes needed**. + +The changes most likely to need app code updates are: + +- [Object syntax removed for `createReducer` and `createSlice.extraReducers`](#object-syntax-for-createsliceextrareducers-and-createreducer-removed) +- [`configureStore.middleware` must be a callback](#configurestoremiddleware-must-be-a-callback) +- [`Middleware` type changed - Middleware `action` and `next` are typed as `unknown`](#middleware-type-changed---middleware-action-and-next-are-typed-as-unknown) + +## Packaging Changes (all) + +We've made updates to the build packaging for all of the Redux-related libraries. These are technically "breaking", but _should_ be transparent to end users, and actually enable better support for scenarios such as using Redux via ESM files under Node. + +#### Addition of `exports` field in `package.json` + +We've migrated the package definitions to include the `exports` field for defining which artifacts to load, with a modern ESM build as the primary artifact (with CJS still included for compatibility purposes). + +We've done local testing of the package, but we ask the community to try out this in your own projects and report any breakages you find! + +#### Build Artifact Modernization + +We've updated the build output in several ways: + +- **Build output is no longer transpiled!** Instead we target modern JS syntax (ES2020) +- Moved all build artifacts to live under ./dist/, instead of separate top-level folders +- The lowest Typescript version we test against is now 4.7 + +#### Dropping UMD builds + +Redux has always shipped with UMD build artifacts. These are primarily meant for direct import as script tags, such as in a CodePen or a no-bundler build environment. + +For now, we're dropping those build artifacts from the published package, on the grounds that the use cases seem pretty rare today. + +We do have a browser-ready ESM build artifact included at `dist/$PACKAGE_NAME.browser.mjs`, which can be loaded via a script tag that points to that file on Unpkg. + +If you have strong use cases for us continuing to include UMD build artifacts, please let us know! + +## Breaking Changes + +### Core + +#### Action types _must_ be strings + +We've always specifically told our users that [actions and state _must_ be serializable](https://redux.js.org/style-guide/#do-not-put-non-serializable-values-in-state-or-actions), and that `action.type` _should_ be a string. This is both to ensure that actions are serializable, and to help provide a readable action history in the Redux DevTools. + +`store.dispatch(action)` now specifically enforces that `action.type` _must_ be a string and will throw an error if not, in the same way it throws an error if the action is not a plain object. + +In practice, this was already true 99.99% of the time and shouldn't have any effect on users (especially those using Redux Toolkit and `createSlice`), but there may be some legacy Redux codebases that opted to use Symbols as action types. + +#### `createStore` Deprecation + +In [Redux 4.2.0, we marked the original `createStore` method as `@deprecated`](https://github.com/reduxjs/redux/releases/tag/v4.2.0). Strictly speaking, **this is _not_ a breaking change**, nor is it new in 5.0, but we're documenting it here for completeness. + +**This deprecation is solely a _visual_ indicator that is meant to encourage users to [migrate their apps from legacy Redux patterns to use the modern Redux Toolkit APIs](https://redux.js.org/usage/migrating-to-modern-redux)**. The deprecation results in a visual strikethrough when imported and used, like ~~`createStore`~~, but with _no_ runtime errors or warnings. + +**`createStore` will continue to work indefinitely, and will _not_ ever be removed**. But, today we want _all_ Redux users to be using Redux Toolkit for all of their Redux logic. + +To fix this, there are three options: + +- **[Follow our strong suggestion to switch over to Redux Toolkit and `configureStore`](https://redux.js.org/usage/migrating-to-modern-redux)** +- Do nothing. It's just a visual strikethrough, and it doesn't affect how your code behaves. Ignore it. +- Switch to using the `legacy_createStore` API that is now exported, which is the exact same function but with no `@deprecated` tag. The simplest option is to do an aliased import rename, like `import { legacy_createStore as createStore } from 'redux'` + +
+ +#### Typescript rewrite + +In 2019, we began a community-powered conversion of the Redux codebase to TypeScript. The original effort was discussed in [#3500: Port to TypeScript](https://github.com/reduxjs/redux/issues/3500), and the work was integrated in PR [#3536: Convert to TypeScript](https://github.com/reduxjs/redux/issues/3536). + +However, the TS-converted code in master has sat around since then, unused and unpublished, due to concerns about possible compatibility issues with the existing ecosystem (as well as general inertia on our part). + +Redux core v5 is now built from that TS-converted source code. In theory, this should be almost identical in both runtime behavior and types to the 4.x build, but it's very likely that some of the changes may cause types issues. + +Please report any unexpected compatibility issues on [Github](https://github.com/reduxjs/redux/issues)! + +#### `AnyAction` deprecated in favour of `UnknownAction` + +The Redux TS types have always exported an `AnyAction` type, which is defined to have `{type: string}` and treat any other field as `any`. This makes it easy to write uses like `console.log(action.whatever)`, but unfortunately does not provide any meaningful type safety. + +We now export an `UnknownAction` type, which treats all fields other than `action.type` as `unknown`. This encourages users to write type guards that check the action object and assert its _specific_ TS type. Inside of those checks, you can access a field with better type safety. + +`UnknownAction` is now the default any place in the Redux source that expects an action object. + +`AnyAction` still exists for compatibility, but has been marked as deprecated. + +Note that [Redux Toolkit's action creators have a `.match()` method](https://redux-toolkit.js.org/api/createAction#actioncreatormatch) that acts as a useful type guard: + +```ts +if (todoAdded.match(someUnknownAction)) { + // action is now typed as a PayloadAction +} +``` + +You can also use the new `isAction` util to check if an unknown value is some kind of action object. + +#### `Middleware` type changed - Middleware `action` and `next` are typed as `unknown` + +Previously, the `next` parameter is typed as the `D` type parameter passed, and `action` is typed as the `Action` extracted from the dispatch type. Neither of these are a safe assumption: + +- `next` would be typed to have **all** of the dispatch extensions, including the ones earlier in the chain that would no longer apply. + - Technically it would be _mostly_ safe to type `next` as the default Dispatch implemented by the base redux store, however this would cause `next(action)` to error (as we cannot promise `action` is actually an `Action`) - and it wouldn't account for any following middlewares that return anything other than the action they're given when they see a specific action. +- `action` is not necessarily a known action, it can be literally anything - for example a thunk would be a function with no `.type` property (so `AnyAction` would be inaccurate) + +We've changed `next` to be `(action: unknown) => unknown` (which is accurate, we have no idea what `next` expects or will return), and changed the `action` parameter to be `unknown` (which as above, is accurate). + +In order to safely interact with values or access fields inside of the `action` argument, you must first do a type guard check to narrow the type, such as `isAction(action)` or `someActionCreator.match(action)`. + +This new type is incompatible with the v4 `Middleware` type, so if a package's middleware is saying it's incompatible, check which version of Redux it's getting its types from! + +#### `PreloadedState` type removed in favour of `Reducer` generic + +We've made tweaks to the TS types to improve type safety and behavior. + +First, the `Reducer` type now has a `PreloadedState` possible generic: + +```ts +type Reducer = ( + state: S | PreloadedState | undefined, + action: A +) => S +``` + +Per the explanation in [#4491](https://github.com/reduxjs/redux/pull/4491): + +Why the need for this change? When the store is first created by `createStore`/`configureStore`, the initial state is set to whatever is passed as the `preloadedState` argument (or `undefined` if nothing is passed). That means that the first time that the reducer is called, it is called with the `preloadedState`. After the first call, the reducer is always passed the current state (which is `S`). + +For most normal reducers, `S | undefined` accurately describes what can be passed in for the `preloadedState`. However the `combineReducers` function allows for a preloaded state of `Partial | undefined`. + +The solution is to have a separate generic that represents what the reducer accepts for its preloaded state. That way `createStore` can then use that generic for its `preloadedState` argument. + +Previously, this was handled by a `$CombinedState` type, but that complicated things and led to some user-reported issues. This removes the need for `$CombinedState` altogether. + +This change does include some breaking changes, but overall should not have a huge impact on users upgrading in user-land: + +- The `Reducer`, `ReducersMapObject`, and `createStore`/`configureStore` types/function take an additional `PreloadedState` generic which defaults to `S`. +- The overloads for `combineReducers` are removed in favor of a single function definition that takes the `ReducersMapObject` as its generic parameter. Removing the overloads was necessary with these changes, since sometimes it was choosing the wrong overload. +- Enhancers that explicitly list the generics for the reducer will need to add the third generic. + +
+ +### Toolkit only + +#### Object syntax for `createSlice.extraReducers` and `createReducer` removed + +RTK's `createReducer` API was originally designed to accept a lookup table of action type strings to case reducers, like `{ "ADD_TODO": (state, action) => {} }`. We later added the "builder callback" form to allow more flexibility in adding "matchers" and a default handler, and did the same for `createSlice.extraReducers`. + +We have removed the "object" form for both `createReducer` and `createSlice.extraReducers` in RTK 2.0, as the builder callback form is effectively the same number of lines of code, and works much better with TypeScript. + +As an example, this: + +```ts +const todoAdded = createAction('todos/todoAdded') + +createReducer(initialState, { + [todoAdded]: (state, action) => {} +}) + +createSlice({ + name, + initialState, + reducers: { + /* case reducers here */ + }, + extraReducers: { + [todoAdded]: (state, action) => {} + } +}) +``` + +should be migrated to: + +```ts +createReducer(initialState, builder => { + builder.addCase(todoAdded, (state, action) => {}) +}) + +createSlice({ + name, + initialState, + reducers: { + /* case reducers here */ + }, + extraReducers: builder => { + builder.addCase(todoAdded, (state, action) => {}) + } +}) +``` + +##### Codemods + +To simplify upgrading codebases, we've published a set of codemods that will automatically transform the deprecated "object" syntax into the equivalent "builder" syntax. + +The codemods package is available on NPM as [`@reduxjs/rtk-codemods`](https://www.npmjs.com/package/@reduxjs/rtk-codemods). More details are available [here](https://redux-toolkit.js.org/api/codemods). + +To run the codemods against your codebase, run `npx @reduxjs/rtk-codemods path/of/files/ or/some**/*glob.js.` + +Examples: + +```sh +npx @reduxjs/rtk-codemods createReducerBuilder ./src + +npx @reduxjs/rtk-codemods createSliceBuilder ./packages/my-app/**/*.ts +``` + +We also recommend re-running Prettier on the codebase before committing the changes. + +These codemods should work, but we would greatly appreciate feedback from more real-world codebases! + +#### `configureStore.middleware` must be a callback + +Since the beginning, `configureStore` has accepted a direct array value as the `middleware` option. However, providing an array directly prevents `configureStore` from calling `getDefaultMiddleware()`. So, `middleware: [myMiddleware]` means there is no thunk middleware added (or any of the dev-mode checks). + +This is a footgun, and we've had numerous users accidentally do this and cause their apps to fail because the default middleware never got configured. + +As a result, we've now made the `middleware` only accept the callback form. _If_ for some reason you still want to replace _all_ of the built-in middleware, do so by returning an array from the callback: + +```ts +const store = configureStore({ + reducer, + middleware: getDefaultMiddleware => { + // WARNING: this means that _none_ of the default middleware are added! + return [myMiddleware] + // or for TS users, use: + // return new Tuple(myMiddleware) + } +}) +``` + +But note that **we consistently recommend not replacing the default middleware entirely**, and that you should use `return getDefaultMiddleware().concat(myMiddleware)`. + +#### `configureStore.enhancers` must be a callback + +Similarly to `configureStore.middleware`, the `enhancers` field must also be a callback, for the same reasons. + +The callback will receive a `getDefaultEnhancers` function that can be used to customise the batching enhancer [that's now included by default](#configurestore-adds-autobatchenhancer-by-default). + +For example: + +```ts +const store = configureStore({ + reducer, + enhancers: getDefaultEnhancers => { + return getDefaultEnhancers({ + autoBatch: { type: 'tick' } + }).concat(myEnhancer) + } +}) +``` + +It's important to note that the result of `getDefaultEnhancers` will **also** contain the middleware enhancer created with any configured/default middleware. To help prevent mistakes, `configureStore` will log an error to console if middleware was provided and the middleware enhancer wasn't included in the callback result. + +```ts +const store = configureStore({ + reducer, + enhancers: getDefaultEnhancers => { + return [myEnhancer] // we've lost the middleware here + // instead: + return getDefaultEnhancers().concat(myEnhancer) + } +}) +``` + +#### Standalone `getDefaultMiddleware` and `getType` removed + +The standalone version of `getDefaultMiddleware` has been deprecated since v1.6.1, and has now been removed. Use the function passed to the `middleware` callback instead, which has the correct types. + +We have also removed the `getType` export, which was used to extract a type string from action creators made with `createAction`. Instead, use the static property `actionCreator.type`. + +#### RTK Query behaviour changes + +We've had a number of reports where RTK Query had issues around usage of `dispatch(endpoint.initiate(arg, {subscription: false}))`. There were also reports that multiple triggered lazy queries were resolving the promises at the wrong time. Both of these had the same underlying issue, which was that RTKQ wasn't tracking cache entries in these cases (intentionally). We've reworked the logic to always track cache entries (and remove them as needed), which should resolve those behavior issues. + +We also have had issues raised about trying to run multiple mutations in a row and how tag invalidation behaves. RTKQ now has internal logic to delay tag invalidation briefly, to allow multiple invalidations to get handled together. This is controlled by a new `invalidationBehavior: 'immediate' | 'delayed'` flag on `createApi`. The new default behavior is `'delayed'`. Set it to `'immediate'` to revert to the behavior in RTK 1.9. + +In RTK 1.9, we reworked RTK Query's internals to keep most of the subscription status inside the RTKQ middleware. The values are still synced to the Redux store state, but this is primarily for display by the Redux DevTools "RTK Query" panel. Related to the cache entry changes above, we've optimized how often those values get synced to the Redux state for perf. + +#### `reactHooksModule` custom hook configuration + +Previously, custom versions of React Redux's hooks (`useSelector`, `useDispatch`, and `useStore`) could be passed separately to `reactHooksModule`, usually to enable using a different context to the default `ReactReduxContext`. + +In practicality, the react hooks module needs all three of these hooks to be provided, and it became an easy mistake to only pass `useSelector` and `useDispatch`, without `useStore`. + +The module has now moved all three of these under the same configuration key, and will check that all three are provided if the key is present. + +```ts +// previously +const customCreateApi = buildCreateApi( + coreModule(), + reactHooksModule({ + useDispatch: createDispatchHook(MyContext), + useSelector: createSelectorHook(MyContext), + useStore: createStoreHook(MyContext) + }) +) + +// now +const customCreateApi = buildCreateApi( + coreModule(), + reactHooksModule({ + hooks: { + useDispatch: createDispatchHook(MyContext), + useSelector: createSelectorHook(MyContext), + useStore: createStoreHook(MyContext) + } + }) +) +``` + +#### Error message extraction + +Redux 4.1.0 optimized its bundle size by [extracting error message strings out of production builds](https://github.com/reduxjs/redux/releases/tag/v4.1.0), based on React's approach. We've applied the same technique to RTK. This saves about 1000 bytes from prod bundles (actual benefits will depend on which imports are being used). + +
+ +#### Non-default middleware/enhancers must use `Tuple` + +We've seen many cases where users passing the `middleware` parameter to configureStore have tried spreading the array returned by `getDefaultMiddleware()`, or passed an alternate plain array. This unfortunately loses the exact TS types from the individual middleware, and often causes TS problems down the road (such as `dispatch` being typed as `Dispatch` and not knowing about thunks). + +`getDefaultMiddleware()` already used an internal `MiddlewareArray` class, an `Array` subclass that had strongly typed `.concat/prepend()` methods to correctly capture and retain the middleware types. + +We've renamed that type to `Tuple`, and `configureStore`'s TS types now require that you _must_ use `Tuple` if you want to pass your own array of middleware: + +```ts +import { configureStore, Tuple } from '@reduxjs/toolkit' + +configureStore({ + reducer: rootReducer, + middleware: getDefaultMidldeware => new Tuple(additionalMiddleware, logger) +}) +``` + +(Note that this has no effect if you're using RTK with plain JS, and you could still pass a plain array here.) + +This same restriction applies to the `enhancers` field. + +#### Entity adapter type updates + +`createEntityAdapter` now has an `Id` generic argument, which will be used to strongly type the item IDs anywhere those are exposed. Previously, the ID field type was always `string | number`. TS will now try to infer the exact type from either the `.id` field of your entity type, or the `selectId` return type. You could also fall back to passing that generic type directly. + +The `.entities` lookup table is now defined to use a standard TS `Record`, which assumes that each item lookup exists by default. Previously, it used a `Dictionary` type, which assumed the result was `MyEntityType | undefined`. The `Dictionary` type has been removed. + +If you prefer to assume that the lookups _might_ be undefined, use TypeScript's `noUncheckedIndexedAccess` configuration option to control that. + +
+ +## New Features + +These features are new in Redux Toolkit 2.0, and help cover additional use cases that we've seen users ask for in the ecosystem. + +### `combineSlices` API with slice reducer injection for code-splitting + +The Redux core has always included `combineReducers`, which takes an object full of "slice reducer" functions and generates a reducer that calls those slice reducers. RTK's `createSlice` generates slice reducers + associated action creators, and we've taught the pattern of exporting individual action creators as named exports and the slice reducer as a default export. Meanwhile, we've never had official support for lazy-loading reducers, although we've had [sample code for some "reducer injection" patterns in our docs](https://redux.js.org/usage/code-splitting). + +This release includes a new [`combineSlices`](https://redux-toolkit.js.org/api/combineSlices) API that is designed to enable lazy-loading of reducers at runtime. It accepts individual slices or an object full of slices as arguments, and automatically calls `combineReducers` using the `sliceObject.name` field as the key for each state field. The generated reducer function has an additional `.inject()` method attached that can be used to dynamically inject additional slices at runtime. It also includes a `.withLazyLoadedSlices()` method that can be used to generate TS types for reducers that will be added later. See [#2776](https://github.com/reduxjs/redux-toolkit/issues/2776) for the original discussion around this idea. + +For now, we are not building this into `configureStore`, so you'll need to call `const rootReducer = combineSlices(.....)` yourself and pass that to `configureStore({reducer: rootReducer})`. + +**Basic usage: a mixture of slices and standalone reducers passed to `combineSlices`** + +```ts +const stringSlice = createSlice({ + name: 'string', + initialState: '', + reducers: {} +}) + +const numberSlice = createSlice({ + name: 'number', + initialState: 0, + reducers: {} +}) + +const booleanReducer = createReducer(false, () => {}) + +const api = createApi(/* */) + +const combinedReducer = combineSlices( + stringSlice, + { + num: numberSlice.reducer, + boolean: booleanReducer + }, + api +) +expect(combinedReducer(undefined, dummyAction())).toEqual({ + string: stringSlice.getInitialState(), + num: numberSlice.getInitialState(), + boolean: booleanReducer.getInitialState(), + api: api.reducer.getInitialState() +}) +``` + +**Basic slice reducer injection** + +```ts +// Create a reducer with a TS type that knows `numberSlice` will be injected +const combinedReducer = + combineSlices(stringSlice).withLazyLoadedSlices< + WithSlice + >() + +// `state.number` doesn't exist initially +expect(combinedReducer(undefined, dummyAction()).number).toBe(undefined) + +// Create a version of the reducer with `numberSlice` injected (mainly useful for types) +const injectedReducer = combinedReducer.inject(numberSlice) + +// `state.number` now exists, and injectedReducer's type no longer marks it as optional +expect(injectedReducer(undefined, dummyAction()).number).toBe( + numberSlice.getInitialState() +) + +// original reducer has also been changed (type is still optional) +expect(combinedReducer(undefined, dummyAction()).number).toBe( + numberSlice.getInitialState() +) +``` + +### `selectors` field in `createSlice` + +The existing `createSlice` API now has support for defining [`selectors`](https://redux-toolkit.js.org/api/createSlice#selectors) directly as part of the slice. By default, these will be generated with the assumption that the slice is mounted in the root state using `slice.name` as the field, such as `name: "todos"` -> `rootState.todos`. You can call `sliceObject.getSelectors(selectSliceState)` to generate the selectors with an alternate location, similar to how `entityAdapter.getSelectors()` works. + +```ts +const slice = createSlice({ + name: 'counter', + initialState: 42, + reducers: {}, + selectors: { + selectSlice: state => state, + selectMultiple: (state, multiplier: number) => state * multiplier + } +}) + +// Basic usage +const testState = { + [slice.name]: slice.getInitialState() +} +const { selectSlice, selectMultiple } = slice.selectors +expect(selectSlice(testState)).toBe(slice.getInitialState()) +expect(selectMultiple(testState, 2)).toBe(slice.getInitialState() * 2) + +// Usage with the slice reducer mounted under a different key +const customState = { + number: slice.getInitialState() +} +const { selectSlice, selectMultiple } = slice.getSelectors( + (state: typeof customState) => state.number +) +expect(selectSlice(customState)).toBe(slice.getInitialState()) +expect(selectMultiple(customState, 2)).toBe(slice.getInitialState() * 2) +``` + +### `createSlice.reducers` callback syntax and thunk support + +One of the oldest feature requests we've had is the ability to declare thunks directly inside of `createSlice`. Until now, you've always had to declare them separately, give the thunk a string action prefix, and handle the actions via `createSlice.extraReducers`: + +```ts +// Declare the thunk separately +const fetchUserById = createAsyncThunk( + 'users/fetchByIdStatus', + async (userId: number, thunkAPI) => { + const response = await userAPI.fetchById(userId) + return response.data + } +) + +const usersSlice = createSlice({ + name: 'users', + initialState, + reducers: { + // standard reducer logic, with auto-generated action types per reducer + }, + extraReducers: builder => { + // Add reducers for additional action types here, and handle loading state as needed + builder.addCase(fetchUserById.fulfilled, (state, action) => { + state.entities.push(action.payload) + }) + } +}) +``` + +Many users have told us that this separation feels awkward. + +We've _wanted_ to include a way to define thunks directly inside of `createSlice`, and have played around with various prototypes. There were always two major blocking issues, and a secondary concern: + +1. It wasn't clear what the syntax for declaring a thunk inside should look like. +2. Thunks have access to `getState` and `dispatch`, but the `RootState` and `AppDispatch` types are normally inferred from the store, which in turn infers it from the slice state types. Declaring thunks inside `createSlice` would cause circular type inference errors, as the store needs the slice types but the slice needs the store types. We weren't willing to ship an API that would work okay for our JS users but not for our TS users, especially since we _want_ people to use TS with RTK. +3. You can't do synchronous conditional imports in ES modules, and there's no good way to make the `createAsyncThunk` import optional. Either `createSlice` always depends on it (and adds that to the bundle size), or it can't use `createAsyncThunk` at all. + +We've settled on these compromises: + +- **In order to create async thunks with `createSlice`, you specifically need to [set up a custom version of `createSlice` that has access to `createAsyncThunk`]https://redux-toolkit.js.org/api/createSlice#createasyncthunk)**. +- You can declare thunks inside of `createSlice.reducers`, by using a "creator callback" syntax for the `reducers` field that is similar to the `build` callback syntax in RTK Query's `createApi` (using typed functions to create fields in an object). Doing this does look a bit different than the existing "object" syntax for the `reducers` field, but is still fairly similar. +- You can customize _some_ of the types for thunks inside of `createSlice`, but you _cannot_ customize the `state` or `dispatch` types. If those are needed, you can manually do an `as` cast, like `getState() as RootState`. + +In practice, we hope these are reasonable tradeoffs. Creating thunks inside of `createSlice` has been widely asked for, so we think it's an API that will see usage. If the TS customization options are a limitation, you can still declare thunks outside of `createSlice` as always, and most async thunks don't need `dispatch` or `getState` - they just fetch data and return. And finally, setting up a custom `createSlice` allows you to opt into `createAsyncThunk` being included in your bundle size (though it may already be included if used directly or as part of RTK Query - in either of these cases there's no _additional_ bundle size). + +Here's what the new callback syntax looks like: + +```ts +const createSliceWithThunks = buildCreateSlice({ + creators: { asyncThunk: asyncThunkCreator } +}) + +const todosSlice = createSliceWithThunks({ + name: 'todos', + initialState: { + loading: false, + todos: [], + error: null + } as TodoState, + reducers: create => ({ + // A normal "case reducer", same as always + deleteTodo: create.reducer((state, action: PayloadAction) => { + state.todos.splice(action.payload, 1) + }), + // A case reducer with a "prepare callback" to customize the action + addTodo: create.preparedReducer( + (text: string) => { + const id = nanoid() + return { payload: { id, text } } + }, + // action type is inferred from prepare callback + (state, action) => { + state.todos.push(action.payload) + } + ), + // An async thunk + fetchTodo: create.asyncThunk( + // Async payload function as the first argument + async (id: string, thunkApi) => { + const res = await fetch(`myApi/todos?id=${id}`) + return (await res.json()) as Item + }, + // An object containing `{pending?, rejected?, fulfilled?, settled?, options?}` second + { + pending: state => { + state.loading = true + }, + rejected: (state, action) => { + state.error = action.payload ?? action.error + }, + fulfilled: (state, action) => { + state.todos.push(action.payload) + }, + // settled is called for both rejected and fulfilled actions + settled: (state, action) => { + state.loading = false + } + } + ) + }) +}) + +// `addTodo` and `deleteTodo` are normal action creators. +// `fetchTodo` is the async thunk +export const { addTodo, deleteTodo, fetchTodo } = todosSlice.actions +``` + +#### Codemod + +**Using the new callback syntax is entirely optional (the object syntax is still standard)**, but an existing slice would need to be converted before it can take advantage of the new capabilities this syntax provides. To make this easier, a [codemod](https://redux-toolkit.js.org/api/codemods) is provided. + +```sh +npx @reduxjs/rtk-codemods createSliceReducerBuilder ./src/features/todos/slice.ts +``` + +### "Dynamic middleware" middleware + +A Redux store's middleware pipeline is fixed at store creation time and can't be changed later. We _have_ seen ecosystem libraries that tried to allow dynamically adding and removing middleware, potentially useful for things like code splitting. + +This is a relatively niche use case, but we've built [our own version of a "dynamic middleware" middleware](https://redux-toolkit.js.org/api/createDynamicMiddleware). Add it to the Redux store at setup time, and it lets you add middleware later at runtime. It also comes with a [React hook integration that will automatically add a middleware to the store and return the updated dispatch method.](https://redux-toolkit.js.org/api/createDynamicMiddleware#react-integration). + +```ts +import { createDynamicMiddleware, configureStore } from '@reduxjs/toolkit' + +const dynamicMiddleware = createDynamicMiddleware() + +const store = configureStore({ + reducer: { + todos: todosReducer + }, + middleware: getDefaultMiddleware => + getDefaultMiddleware().prepend(dynamicMiddleware.middleware) +}) + +// later +dynamicMiddleware.addMiddleware(someOtherMiddleware) +``` + +### `configureStore` adds `autoBatchEnhancer` by default + +[In v1.9.0, we added a new `autoBatchEnhancer`](https://github.com/reduxjs/redux-toolkit/releases/tag/v1.9.0) that delays notifying subscribers briefly when multiple "low-priority" actions are dispatched in a row. This improves perf, as UI updates are typically the most expensive part of the update process. RTK Query marks most of its own internal actions as "low-pri" by default, but you have to have the `autoBatchEnhancer` added to the store to benefit from that. + +We've updated `configureStore` to add the `autoBatchEnhancer` to the store setup by default, so that users can benefit from the improved perf without needing to manually tweak the store config themselves. + +### `entityAdapter.getSelectors` accepts a `createSelector` function + +[`entityAdapter.getSelectors()`](https://redux-toolkit.js.org/api/createEntityAdapter#selector-functions) now accepts an options object as its second argument. This allows you to pass in your own preferred `createSelector` method, which will be used to memoize the generated selectors. This could be useful if you want to use one of Reselect's new alternate memoizers, or some other memoization library with an equivalent signature. + +### New dev checks in Reselect v5 + +Reselect v5 includes a dev-only check to check stability of input selectors, by running them an extra time with the same parameters, and checking that the result matches. + +This is important, as an input selector returning a materially different result with the same parameters means that the output selector will be run unnecessarily, thus (potentially) creating a new result and causing rerenders. + +```ts +const addNumbers = createSelector( + // this input selector will always return a new reference when run + // so cache will never be used + (a, b) => ({ a, b }), + ({ a, b }) => ({ total: a + b }) +) +// instead, you should have an input selector for each stable piece of data +const addNumbersStable = createSelector( + (a, b) => a, + (a, b) => b, + (a, b) => ({ + total: a + b + }) +) +``` + +This is done the first time the selector is called, unless configured otherwise. More details are available in the [README](https://github.com/reduxjs/reselect#inputstabilitycheck). + +Note that RTK intentionally does not re-export the function to configure this check globally - if you wish to do so, you should instead depend on `reselect` directly and import it yourself. + +### Immer 10.0 + +[Immer 10.0](https://github.com/immerjs/immer/releases/tag/v10.0.0) is now final, and has several major improvements and updates: + +- Much faster update perf +- Much smaller bundle size +- Better ESM/CJS package formatting +- No default export +- No ES5 fallback + +We've updated RTK to depend on the final Immer 10.0 release. + +## Recommendations + +Based on changes in 2.0 and previous versions, there have been some shifts in thinking that are good to know about, if non-essential. + +### Alternatives to `actionCreator.toString()` + +As part of RTK's original API, action creators made with `createAction` have a custom `toString()` override that returns the action type. + +This was primarily useful for the ([now removed](#object-syntax-for-createsliceextrareducers-and-createreducer-removed)) object syntax for `createReducer`: + +```ts +const todoAdded = createAction('todos/todoAdded') + +createReducer(initialState, { + [todoAdded]: (state, action) => {} // toString called here, 'todos/todoAdded' +}) +``` + +While this was convenient (and other libraries in the Redux ecosystem such as `redux-saga` and `redux-observable` have supported this to various capacities), it didn't play well with Typescript and was generally a bit too "magic". + +```ts +const test = todoAdded.toString() +// ^? typed as string, rather than specific action type +``` + +Over time, the action creator also gained a static `type` property and `match` method which were more explicit and worked better with Typescript. + +```ts +const test = todoAdded.type +// ^? 'todos/todoAdded' + +// acts as a type predicate +if (todoAdded.match(unknownAction)) { + unknownAction.payload + // ^? now typed as PayloadAction +} +``` + +For compatibility, this override is still in place, but we encourage considering using either of the static properties for more understandable code. + +For example, with `redux-observable`: + +```ts +// before (works in runtime, will not filter types properly) +const epic = (action$: Observable) => + action$.pipe( + ofType(todoAdded), + map(action => action) + // ^? still Action + ) + +// consider (better type filtering) +const epic = (action$: Observable) => + action$.pipe( + filter(todoAdded.match), + map(action => action) + // ^? now PayloadAction + ) +``` + +With `redux-saga`: + +```ts +// before (still works) +yield takeEvery(todoAdded, saga) + +// consider +yield takeEvery(todoAdded.match, saga) +// or +yield takeEvery(todoAdded.type, saga) +``` + +## Future plans + +### Custom slice reducer creators + +With the addition of the [callback syntax for createSlice](#callback-syntax-for-createslicereducers), the [suggestion](https://github.com/reduxjs/redux-toolkit/issues/3837) was made to enable custom slice reducer creators. These creators would be able to: + +- Modify reducer behaviour by adding case or matcher reducers +- Attach actions (or any other useful functions) to `slice.actions` +- Attach provided case reducers to `slice.caseReducers` + +The creator would need to first return a "definition" shape when `createSlice` is first called, which it then handles by adding any necessary reducers and/or actions. + +An API for this is not set in stone, but the existing `create.asyncThunk` creator implemented with a potential API could look like: + +```js +const asyncThunkCreator = { + type: ReducerType.asyncThunk, + define(payloadCreator, config) { + return { + type: ReducerType.asyncThunk, // needs to match reducer type, so correct handler can be called + payloadCreator, + ...config + } + }, + handle( + { + // the key the reducer was defined under + reducerName, + // the autogenerated action type, i.e. `${slice.name}/${reducerName}` + type + }, + // the definition from define() + definition, + // methods to modify slice + context + ) { + const { payloadCreator, options, pending, fulfilled, rejected, settled } = + definition + const asyncThunk = createAsyncThunk(type, payloadCreator, options) + + if (pending) context.addCase(asyncThunk.pending, pending) + if (fulfilled) context.addCase(asyncThunk.fulfilled, fulfilled) + if (rejected) context.addCase(asyncThunk.rejected, rejected) + if (settled) context.addMatcher(asyncThunk.settled, settled) + + context.exposeAction(reducerName, asyncThunk) + context.exposeCaseReducer(reducerName, { + pending: pending || noop, + fulfilled: fulfilled || noop, + rejected: rejected || noop, + settled: settled || noop + }) + } +} + +const createSlice = buildCreateSlice({ + creators: { + asyncThunk: asyncThunkCreator + } +}) +``` + +We're not sure how many people/libraries would actually make use of this though, so any feedback over on the [Github issue](https://github.com/reduxjs/redux-toolkit/issues/3837) is welcome! + +### `createSlice.selector` selector factories + +There have been some concerns raised internally about whether `createSlice.selectors` supports memoized selectors sufficiently. You can provide a memoized selector to your `createSlice.selectors` configuration, but you're stuck with that one instance. + +```ts +const todoSlice = createSlice({ + name: 'todos', + initialState: { + todos: [] as Todo[] + }, + reducers: {}, + selectors: { + selectTodosByAuthor = createSelector( + (state: TodoState) => state.todos, + (state: TodoState, author: string) => author, + (todos, author) => todos.filter(todo => todo.author === author) + ) + } +}) + +export const { selectTodosByAuthor } = todoSlice.selectors +``` + +With `createSelector`'s default cache size of 1, this can cause caching issues if called in multiple components with different arguments. One typical solution for this (without `createSlice`) is a [selector factory](https://redux.js.org/usage/deriving-data-selectors#creating-unique-selector-instances): + +```ts +export const makeSelectTodosByAuthor = () => + createSelector( + (state: RootState) => state.todos.todos, + (state: RootState, author: string) => author, + (todos, author) => todos.filter(todo => todo.author === author) + ) + +function AuthorTodos({ author }: { author: string }) { + const selectTodosByAuthor = useMemo(makeSelectTodosByAuthor, []) + const todos = useSelector(state => selectTodosByAuthor(state, author)) +} +``` + +Of course, with `createSlice.selectors` this is no longer possible, as you need the selector instance when creating your slice. + +In 2.0.0 we have no set solution for this - a few APIs have been floated ([PR 1](https://github.com/reduxjs/redux-toolkit/pull/3671), [PR 2](https://github.com/reduxjs/redux-toolkit/pull/3836)) but nothing was decided upon. If this is something you'd like to see supported, consider providing feedback in the [Github discussion](https://github.com/reduxjs/redux-toolkit/discussions/3387)! + +### 3.0 - RTK Query + +RTK 2.0 was largely focused on core and toolkit changes. Now that 2.0 is released, we would like to shift our focus to RTK Query, as there are still some rough edges to iron out - some of which may require breaking changes, necessitating a 3.0 release. + +If you have any feedback for what that could look like, please consider chiming in at the [RTK Query API pain points and rough spots feedback thread](https://github.com/reduxjs/redux-toolkit/issues/3692)! + +
diff --git a/website/sidebars.js b/website/sidebars.js index a83c4dfebd..ddc00f6f8f 100755 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -51,12 +51,20 @@ module.exports = { collapsed: false, items: [ 'usage/configuring-your-store', - 'usage/migrating-to-modern-redux', 'usage/code-splitting', 'usage/server-rendering', 'usage/isolating-redux-sub-apps' ] }, + { + type: 'category', + label: 'Migrations', + collapsed: false, + items: [ + 'usage/migrating-to-modern-redux', + 'usage/migrations/migrating-rtk-2' + ] + }, { type: 'category', label: 'Code Quality', From 68558255d07ac64a1904c6a1c2627cfe5e0ebc4b Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 17:46:16 -0500 Subject: [PATCH 06/16] Paste in Next.js setup doc --- docs/usage/nextjs.mdx | 432 ++++++++++++++++++++++++++++++++++++++++++ website/sidebars.js | 1 + 2 files changed, 433 insertions(+) create mode 100644 docs/usage/nextjs.mdx diff --git a/docs/usage/nextjs.mdx b/docs/usage/nextjs.mdx new file mode 100644 index 0000000000..e24349a545 --- /dev/null +++ b/docs/usage/nextjs.mdx @@ -0,0 +1,432 @@ +--- +id: nextjs +title: Redux Toolkit Setup with Next.js +sidebar_label: Setup with Next.js +hide_title: true +--- + +  + +# Redux Toolkit Setup with Next.js + +:::tip What You'll Learn + +- How to set up and use Redux Toolkit with the [Next.js framework](https://nextjs.org/docs) + +::: + +:::info Prerequisites + +- Familiarity with [ES6 syntax and features](https://www.taniarascia.com/es6-syntax-and-feature-overview/) +- Knowledge of React terminology: [JSX](https://reactjs.org/docs/introducing-jsx.html), [State](https://reactjs.org/docs/state-and-lifecycle.html), [Function Components, Props](https://reactjs.org/docs/components-and-props.html), and [Hooks](https://reactjs.org/docs/hooks-intro.html) +- Understanding of [Redux terms and concepts](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow) +- Working through the [Quick Start tutorial](../tutorials/quick-start.md) and [TypeScript Quick Start tutorial](../tutorials/typescript.md) is recommended, and ideally the full [Redux Essentials](https://redux.js.org/tutorials/essentials/part-1-overview-concepts) tutorial as well + +::: + +## Introduction + +[Next.js](https://nextjs.org/docs) is a popular server side rendering framework for React that presents some unique challenges for using Redux properly. These challenges include: + +- **Per-request safe Redux store creation**: A Next.js server can handle multiple requests simultaneously. This means that the Redux store should be created per request and that the store should not be shared across requests. +- **SSR-friendly store hydration**: Next.js applications are rendered twice, first on the server and again on the client. Failure to render the same page contents on both the client and the server will result in a "hydration error". So the Redux store will have to be initialized on the server and then re-initialized on the client with the same data in order to avoid hydration issues. +- **SPA routing support**: Next.js supports a hybrid model for client side routing. A customer's first page load will get an SSR result from the server. Subsequent page navigation will be handled by the client. This means that with a singleton store defined in the layout, route-specific data will need to be selectively reset on route navigation, while non-route-specific data will need to be retained in the store. +- **Server caching friendly**: Recent versions of Next.js (specifically applications using the App Router architecture) support aggressive server caching. The ideal store architecture should be compatible with this caching. + +There are two architectures for a Next.js application: the [Pages Router](https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts) and the [App Router](https://nextjs.org/docs/app/building-your-application/routing). + +The Pages Router is the original architecture for Next.js. If you're using the Pages Router, Redux setup is primarily handled by using the [`next-redux-wrapper` library](https://github.com/kirill-konshin/next-redux-wrapper), which integrates a Redux store with the Pages Router data fetching methods like `getServerSideProps`. + +**This guide will focus on the App Router architecture**, as it is the new default architecture option for Next.js. + +### How to Read This Guide + +This page assumes that you already have an existing Next.js application based on the App Router architecture. + +If you want to follow along, you can create a new empty Next project with `npx create-next-app my-app` - the default prompts will set up a new project with the App Router enabled. Then, add `@reduxjs/toolkit` and `react-redux` as dependencies. + +You can also create a new Next+Redux project with `npx create-next-app --example with-redux my-app`, which includes the initial setup pieces described in this page. + +## The App Router Architecture and Redux + +The primary new feature of the Next.js App Router is the addition of support for React Server Components (RSCs). RSCs are a special type of React component that only renders on the server, as opposed to "client" components that render on **both** the client and the server. RSCs can be defined as `async` functions and return promises during rendering as they make async requests for data to render. + +RSCs ability to block for data requests means that with the App Router you no longer have `getServerSideProps` to fetch data for rendering. Any component in the tree can make asychronous requests for data. While this is very convenient it also means thats if you define global variables (like the Redux store) they will be shared across requests. This is a problem because the Redux store could be contaminated with data from other requests. + +Based on the architecture of the App Router we have these general recommendations for appropriate use of Redux: + +- **No global stores** - Because the Redux store is shared across requests, it should not be defined as a global variable. Instead, the store should be created per request. +- **RSCs should not read or write the Redux store** - RSCs cannot use hooks or context. They aren't meant to be stateful. Having an RSC read or write values from a global store violates the architecture of the Next.js App Router. +- **The store should only contain mutable data** - We recommend that you use your Redux sparingly for data intended to be global and mutable. + +These recommendations are specific to applications written with the Next.js App Router. Single Page Applications (SPAs) don't execute on the server and therefore can define stores as global variables. SPAs don't need to worry about RSCs since they don't exist in SPAs. And singleton stores can store whatever data you want. + +### Folder Structure + +Next apps can be created to have the `/app` folder either at the root, or nested under `/src/app`. Your Redux logic should go in a separate folder, alongside the `/app` folder. It's common to put the Redux logic in a folder named `/lib`, but not required. + +The file and folder structure inside of that `/lib` folder is up to you, but we generally recommend [a "feature folder"-based structure](https://redux.js.org/style-guide/#structure-files-as-feature-folders-with-single-file-logic) for the Redux logic. + +A typical example might look like: + +``` +/app + layout.tsx + page.tsx + StoreProvider.tsx +/lib + store.ts + /features + /todos + todosSlice.ts +``` + +We'll use that approach for this guide. + +## Initial Setup + +Similar to the the [RTK TypeScript Tutorial](../tutorials/typescript.md), we need to create a file for the Redux store, as well as the inferred `RootState` and `AppDispatch` types. + +However, Next's multi-page architecture requires some differences from that single-page app setup. + +### Creating a Redux Store per Request + +The first change is to move from defining `store` as a global or module-singleton variable, to defining a `makeStore` function that returns a new store for each request: + +```ts title="lib/store.ts" +import { configureStore } from '@reduxjs/toolkit' + +export const makeStore = () => { + return configureStore({ + reducer: {} + }) +} + +// Infer the type of makeStore +export type AppStore = ReturnType +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType +export type AppDispatch = AppStore['dispatch'] +``` + +Now we have a function, `makeStore`, that we can use to create a store instance per-request while retaining the strong type safety (if you choose to use TypeScript) that Redux Toolkit provides. + +We don't have a `store` variable exported, but we can infer the `RootState` and `AppDispatch` types from the return type of `makeStore`. + +You'll also want to create and export [pre-typed versions of the React-Redux hooks as well](../tutorials/typescript.md#define-typed-hooks), to simplify usage later: + +```ts title="lib/hooks.ts" +// file: lib/store.ts noEmit +import { configureStore } from '@reduxjs/toolkit' + +export const makeStore = () => { + return configureStore({ + reducer: {} + }) +} + +// Infer the type of makeStore +export type AppStore = ReturnType +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType +export type AppDispatch = AppStore['dispatch'] + +/* prettier-ignore */ + +// file: lib/hooks.ts +import { useDispatch, useSelector, useStore } from 'react-redux' +import type { TypedUseSelectorHook } from 'react-redux' +import type { RootState, AppDispatch, AppStore } from './store' + +// highlight-start +// Use throughout your app instead of plain `useDispatch` and `useSelector` +export const useAppDispatch: () => AppDispatch = useDispatch +export const useAppSelector: TypedUseSelectorHook = useSelector +export const useAppStore: () => AppStore = useStore +// highlight-end +``` + +### Providing the Store + +To use this new `makeStore` function we need to create a new "client" component that will create the store and share it using the React-Redux `Provider` component. + +```ts title="app/StoreProvider.tsx" +// file: lib/store.ts noEmit +import { configureStore } from '@reduxjs/toolkit' + +export const makeStore = () => { + return configureStore({ + reducer: {} + }) +} + +// Infer the type of makeStore +export type AppStore = ReturnType +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType +export type AppDispatch = AppStore['dispatch'] + +/* prettier-ignore */ + +// file: app/StoreProvider.tsx +'use client' +import { useRef } from 'react' +import { Provider } from 'react-redux' +// highlight-start +import { makeStore, AppStore } from '../lib/store' +// highlight-end + +export default function StoreProvider({ + children +}: { + children: React.ReactNode +}) { + // highlight-start + const storeRef = useRef() + if (!storeRef.current) { + // Create the store instance the first time this renders + storeRef.current = makeStore() + } + // highlight-end + + return {children} +} +``` + +In this example code we are ensuring that this client component is re-render safe by checking the value of the reference to ensure that the store is only created once. This component will only be rendered once per request on the server, but might be re-rendered multiple times on the client if there are stateful client components located above this component in the tree, or if this component also contains other mutable state that causes a re-render. + +:::tip Why Client Components? + +Any component that interacts with the Redux store (creating it, providing it, reading from it, or writing to it) needs to be a client component. This is because **accessing the store requires React context, and context is only available in client components.** + +::: + +The next step is to **include the `StoreProvider` anywhere in the tree above where the store is used**. You can locate the store in the layout component if all the routes using that layout need the store. Or if the store is only used in a specific route you can create and provide the store in that route handler. In all client components further down the tree, you can use the store exactly as you would normally using the hooks provided by `react-redux`. + +### Loading Initial Data + +If you need to initialize the store with data from the parent component, then define that data as a prop on the client `StoreProvider` component and use a Redux action on the slice to set the data in the store as shown below. + +```ts title="src/app/StoreProvider.tsx" +// file: lib/features/counter/counterSlice.ts noEmit +import { createSlice } from '@reduxjs/toolkit' +import type { PayloadAction } from '@reduxjs/toolkit' + +const counterSlice = createSlice({ + name: 'counter', + initialState: { + value: 0 + }, + reducers: { + initializeCount: (state, action: PayloadAction) => { + state.value = action.payload + } + } +}) + +export const { initializeCount } = counterSlice.actions +export default counterSlice.reducer + +// file: lib/store.ts noEmit +import { configureStore } from '@reduxjs/toolkit' +import counterReducer from './features/counter/counterSlice' + +export const makeStore = () => + configureStore({ + reducer: { + counter: counterReducer + } + }) + +// Infer the type of makeStore +export type AppStore = ReturnType +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType +// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} +export type AppDispatch = AppStore['dispatch'] + +/* prettier-ignore */ + +// file: app/StoreProvider.tsx +'use client' +import { useRef } from 'react' +import { Provider } from 'react-redux' +import { makeStore, AppStore } from '../lib/store' +// highlight-start +import { initializeCount } from '../lib/features/counter/counterSlice' +// highlight-end + +export default function StoreProvider({ + count, + children +}: { + count: number + children: React.ReactNode +}) { + const storeRef = useRef(null) + if (!storeRef.current) { + storeRef.current = makeStore() + // highlight-start + storeRef.current.dispatch(initializeCount(count)) + // highlight-end + } + + return {children} +} +``` + +## Additional Configuration + +### Per-route state + +If you use Next.js's support for client side SPA-style navigation by using `next/navigation`, then when customers navigate from page to page only the route component will be re-rendered. This means that if you have a Redux store created and provided in the layout component it will be preserved across route changes. This is not a problem if you are only using the store for global, mutable data. However, if you are using the store for per-route data then you will need to reset the route-specific data in the store when the route changes. + +Shown below is a `ProductName` example component that uses the Redux store to manage the mutable name of a product. The `ProductName` component part of a product detail route. In order to ensure that we have the correct name in the store we need to set the value in the store any time the `ProductName` component is initially rendered, which happens on any route change to the product detail route. + +```ts title="app/ProductName.tsx" +// file: lib/features/product/productSlice.ts noEmit +import { createSlice } from '@reduxjs/toolkit' +import type { PayloadAction } from '@reduxjs/toolkit' + +export interface Product { + name: string +} + +const productSlice = createSlice({ + name: 'product', + initialState: { + name: '' + }, + reducers: { + initializeProduct: (state, action: PayloadAction) => { + state.name = action.payload.name + }, + setProductName: (state, action: PayloadAction) => { + state.name = action.payload + } + } +}) + +export const { initializeProduct, setProductName } = productSlice.actions +export default productSlice.reducer + +// file: lib/store.ts noEmit +import { configureStore } from '@reduxjs/toolkit' +import productReducer from './features/product/productSlice' + +export const makeStore = () => + configureStore({ + reducer: { + product: productReducer + } + }) + +// Infer the type of makeStore +export type AppStore = ReturnType +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType +// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} +export type AppDispatch = AppStore['dispatch'] + +// file: lib/hooks.ts noEmit +import { useDispatch, useSelector, useStore } from 'react-redux' +import type { TypedUseSelectorHook } from 'react-redux' +import type { RootState, AppDispatch, AppStore } from './store' + +// highlight-start +// Use throughout your app instead of plain `useDispatch` and `useSelector` +export const useAppDispatch: () => AppDispatch = useDispatch +export const useAppSelector: TypedUseSelectorHook = useSelector +export const useAppStore: () => AppStore = useStore +// highlight-end + +/* prettier-ignore */ + +// file: app/ProductName.tsx +'use client' +import { useRef } from 'react' +import { useAppSelector, useAppDispatch, useAppStore } from '../lib/hooks' +import { + initializeProduct, + setProductName, + Product +} from '../lib/features/product/productSlice' + +export default function ProductName({ product }: { product: Product }) { + // highlight-start + // Initialize the store with the product information + const store = useAppStore() + const initialized = useRef(false) + if (!initialized.current) { + store.dispatch(initializeProduct(product)) + initialized.current = true + } + const name = useAppSelector(state => state.product.name) + const dispatch = useAppDispatch() + // highlight-end + + return ( + dispatch(setProductName(e.target.value))} + /> + ) +} +``` + +Here we are using the same intialization pattern as before, of dispatching actions to the store, to set the route-specific data. The `initialized` ref is used to ensure that the store is only initialized once per route change. + +It is worth noting that initializing the store with a `useEffect` would not work because `useEffect` only runs on the client. This would result in hydration errors or flicker because the result from a server side render would not match the result from the client side render. + +### Caching + +The App Router has four seperate caches including `fetch` request and route caches. The most likely cache to cause issues is the route cache. If you have an application that accepts login you may have routes (e.g. the home route, `/`) that render different data based on the user you will need to disable the route cache by using the [`dynamic` export from the route handler](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic): + +```ts +export const dynamic = 'force-dynamic' +``` + +After a mutation you should also invalidate the cache by calling (`revalidatePath`)[https://nextjs.org/docs/app/api-reference/functions/revalidatePath] or (`revalidateTag`)[https://nextjs.org/docs/app/api-reference/functions/revalidateTag] as appropriate. + +### RTK Query + +We recommend using RTK Query for data fetching **on the client only**. Data fetching on the server should use `fetch` requests from `async` RSCs. + +You can learn more about Redux Toolkit Query in the [Redux Toolkit Query tutorial](https://redux-toolkit.js.org/tutorials/rtk-query). + +:::note + +In the future, RTK Query may be able to receive data fetched on the server via React Server Components, but that is a future capability that will require changes to both React and RTK Query. + +::: + +## Checking Your Work + +There are three key areas that you should check to ensure that you have set up Redux Toolkit correctly: + +- **Server Side Rendering** - Check the HTML output of the server to ensure that the data in the Redux store is present in the server side rendered output. +- **Route Change** - Navigate between pages on the same route as well as between different routes to ensure that route-specific data is initialized properly. +- **Mutations** - Check that the store is compatible with the Next.js App Router caches by performing a mutation and then navigating away from the route and back to the original route to ensure that the data is updated. + +## Overall Recommendations + +The App Router presents a dramatically different archtecture for React applications from either the Pages Router or a SPA application. We recommend rethinking your approach to state management in the light of this new architecture. In SPA applications it's not unusual to have a large store that contains all the data, both mutable and immutable, required to drive the application. For App Router applications we recommend that you should: + +- **only use Redux for globally shared, mutable data** +- use a combination of Next.js state (search params, route parameters, form state, etc.), React context and React hooks for all other state management. + +## What You've Learned + +That was a brief overview of how to set up and use Redux Toolkit with the App Router: + +:::tip Summary + +- **Create a Redux store per request by using `configureStore` wrapped in a `makeStore` function** +- **Provide the Redux store to the React application components using a "client" component** +- **Only interact with the Redux store in client components** because only client components have access to React context +- **Use the store as you normally would using the hooks provided in React-Redux** +- **You need to account for the case where you have per-route state in a global store located in the layout** + +## What's Next? + +We recommend going through [**the "Redux Essentials" and "Redux Fundamentals" tutorials in the Redux core docs**](https://redux.js.org/tutorials/index), which will give you a complete understanding of how Redux works, what Redux Toolkit does, and how to use it correctly. diff --git a/website/sidebars.js b/website/sidebars.js index ddc00f6f8f..ffa534748e 100755 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -51,6 +51,7 @@ module.exports = { collapsed: false, items: [ 'usage/configuring-your-store', + 'usage/nextjs', 'usage/code-splitting', 'usage/server-rendering', 'usage/isolating-redux-sub-apps' From 79d7460d758d1de6251e7c8e72e0eaad0d984222 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 18:04:17 -0500 Subject: [PATCH 07/16] Strip down README and move legacy content to "Prior Art" --- README.md | 145 +----------------- .../history-and-design/PriorArt.md | 47 ++++++ 2 files changed, 55 insertions(+), 137 deletions(-) diff --git a/README.md b/README.md index ae0ef360e5..96bee7e914 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,20 @@ # Redux Logo Redux is a predictable state container for JavaScript apps. -(Not to be confused with a WordPress framework – [Redux Framework](https://redux.io)) It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. On top of that, it provides a great developer experience, such as [live code editing combined with a time traveling debugger](https://github.com/reduxjs/redux-devtools). -You can use Redux together with [React](https://react.dev), or with any other view library. -The Redux core tiny (2kB, including dependencies), and has a rich ecosystem of addons. +You can use Redux together with [React](https://react.dev), or with any other view library. The Redux core is tiny (2kB, including dependencies), and has a rich ecosystem of addons. + +[**Redux Toolkit**](https://redux-toolkit.js.org) is our official recommended approach for writing Redux logic. It wraps around the Redux core, and contains packages and functions that we think are essential for building a Redux app. Redux Toolkit builds in our suggested best practices, simplifies most Redux tasks, prevents common mistakes, and makes it easier to write Redux applications. ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/reduxjs/redux/test.yaml?branch=master&event=push&style=flat-square) [![npm version](https://img.shields.io/npm/v/redux.svg?style=flat-square)](https://www.npmjs.com/package/redux) [![npm downloads](https://img.shields.io/npm/dm/redux.svg?style=flat-square)](https://www.npmjs.com/package/redux) [![redux channel on discord](https://img.shields.io/badge/discord-%23redux%20%40%20reactiflux-61dafb.svg?style=flat-square)](https://discord.gg/0ZcbPKXt5bZ6au5t) -[![Changelog #187](https://img.shields.io/badge/changelog-%23187-lightgrey.svg?style=flat-square)](https://changelog.com/187) ## Installation -[**Redux Toolkit**](https://redux-toolkit.js.org) is our official recommended approach for writing Redux logic. It wraps around the Redux core, and contains packages and functions that we think are essential for building a Redux app. Redux Toolkit builds in our suggested best practices, simplifies most Redux tasks, prevents common mistakes, and makes it easier to write Redux applications. - ``` npm install @reduxjs/toolkit react-redux ``` @@ -32,7 +29,7 @@ For more details, see [the Installation docs page](https://redux.js.org/introduc ## Documentation -The Redux docs are located at **https://redux.js.org**: +The Redux core docs are located at **https://redux.js.org**, and include the full Redux tutorials, as well usage guides on general Redux patterns: - [Introduction](https://redux.js.org/introduction/getting-started) - [Tutorials](https://redux.js.org/tutorials/index) @@ -40,6 +37,8 @@ The Redux docs are located at **https://redux.js.org**: - [FAQ](https://redux.js.org/faq) - [API Reference](https://redux.js.org/api/api-reference) +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. + ## Learn Redux ### Redux Essentials Tutorial @@ -50,20 +49,6 @@ The [**Redux Essentials tutorial**](https://redux.js.org/tutorials/essentials/pa The [**Redux Fundamentals tutorial**](https://redux.js.org/tutorials/fundamentals/part-1-overview) is a "bottom-up" tutorial that teaches "how Redux works" from first principles and without any abstractions, and why standard Redux usage patterns exist. -### Additional Tutorials - -- The Redux repository contains several example projects demonstrating various aspects of how to use Redux. Almost all examples have a corresponding CodeSandbox sandbox. This is an interactive version of the code that you can play with online. See the complete list of examples in the **[Examples page](https://redux.js.org/introduction/examples)**. -- Redux creator Dan Abramov's **free ["Getting Started with Redux" video series](https://app.egghead.io/playlists/fundamentals-of-redux-course-from-dan-abramov-bd5cc867)** and **[Building React Applications with Idiomatic Redux](https://egghead.io/courses/building-react-applications-with-idiomatic-redux)** video courses on Egghead.io -- Redux maintainer Mark Erikson's **["Redux Fundamentals" conference talk](https://blog.isquaredsoftware.com/2018/03/presentation-reactathon-redux-fundamentals/)** and [**"Redux Fundamentals" workshop slides**](https://blog.isquaredsoftware.com/2018/06/redux-fundamentals-workshop-slides/) -- Dave Ceddia's post [**A Complete React Redux Tutorial for Beginners**](https://daveceddia.com/redux-tutorial/) - -### Other Resources - -- The **[Redux FAQ](https://redux.js.org/faq)** answers many common questions about how to use Redux, and the **["Using Redux" docs section](https://redux.js.org/usage/index)** has information on handling derived data, testing, structuring reducer logic, and reducing boilerplate. -- Redux maintainer Mark Erikson's **["Practical Redux" tutorial series](https://blog.isquaredsoftware.com/series/practical-redux/)** demonstrates real-world intermediate and advanced techniques for working with React and Redux (also available as **[an interactive course on Educative.io](https://www.educative.io/collection/5687753853370368/5707702298738688)**). -- The **[React/Redux links list](https://github.com/markerikson/react-redux-links)** has categorized articles on working with [reducers and selectors](https://github.com/markerikson/react-redux-links/blob/master/redux-reducers-selectors.md), [managing side effects](https://github.com/markerikson/react-redux-links/blob/master/redux-side-effects.md), [Redux architecture and best practices](https://github.com/markerikson/react-redux-links/blob/master/redux-architecture.md), and more. -- Our community has created thousands of Redux-related libraries, addons, and tools. The **["Ecosystem" docs page](https://redux.js.org/introduction/ecosystem)** lists our recommendations, and also there's a complete listing available in the **[Redux addons catalog](https://github.com/markerikson/redux-ecosystem-links)**. - ### Help and Discussion The **[#redux channel](https://discord.gg/0ZcbPKXt5bZ6au5t)** of the **[Reactiflux Discord community](https://www.reactiflux.com)** is our official resource for all questions related to learning and using Redux. Reactiflux is a great place to hang out, ask questions, and learn - please come and join us there! @@ -88,76 +73,13 @@ Yes, these guidelines are subjective and vague, but this is for a good reason. T > - **[The Tao of Redux, Part 2 - Practice and Philosophy](https://blog.isquaredsoftware.com/2017/05/idiomatic-redux-tao-of-redux-part-2/)** > - **[Redux FAQ](https://redux.js.org/faq)** -## Developer Experience - -Dan Abramov (author of Redux) wrote Redux while working on his React Europe talk called [“Hot Reloading with Time Travel”](https://www.youtube.com/watch?v=xsSnOQynTHs). His goal was to create a state management library with a minimal API but completely predictable behavior. Redux makes it possible to implement logging, hot reloading, time travel, universal apps, record and replay, without any buy-in from the developer. - -## Influences - -Redux evolves the ideas of [Flux](https://facebook.github.io/flux/), but avoids its complexity by taking cues from [Elm](https://github.com/evancz/elm-architecture-tutorial/). -Even if you haven't used Flux or Elm, Redux only takes a few minutes to get started with. - ## Basic Example The whole global state of your app is stored in an object tree inside a single _store_. The only way to change the state tree is to create an _action_, an object describing what happened, and _dispatch_ it to the store. To specify how state gets updated in response to an action, you write pure _reducer_ functions that calculate a new state based on the old state and the action. -```js -import { createStore } from 'redux' - -/** - * This is a reducer - a function that takes a current state value and an - * action object describing "what happened", and returns a new state value. - * A reducer's function signature is: (state, action) => newState - * - * The Redux state should contain only plain JS objects, arrays, and primitives. - * The root state value is usually an object. It's important that you should - * not mutate the state object, but return a new object if the state changes. - * - * You can use any conditional logic you want in a reducer. In this example, - * we use a switch statement, but it's not required. - */ -function counterReducer(state = { value: 0 }, action) { - switch (action.type) { - case 'counter/incremented': - return { value: state.value + 1 } - case 'counter/decremented': - return { value: state.value - 1 } - default: - return state - } -} - -// Create a Redux store holding the state of your app. -// Its API is { subscribe, dispatch, getState }. -let store = createStore(counterReducer) - -// You can use subscribe() to update the UI in response to state changes. -// Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly. -// There may be additional use cases where it's helpful to subscribe as well. - -store.subscribe(() => console.log(store.getState())) - -// The only way to mutate the internal state is to dispatch an action. -// The actions can be serialized, logged or stored and later replayed. -store.dispatch({ type: 'counter/incremented' }) -// {value: 1} -store.dispatch({ type: 'counter/incremented' }) -// {value: 2} -store.dispatch({ type: 'counter/decremented' }) -// {value: 1} -``` - -Instead of mutating the state directly, you specify the mutations you want to happen with plain objects called _actions_. Then you write a special function called a _reducer_ to decide how every action transforms the entire application's state. - -In a typical Redux app, there is just a single store with a single root reducing function. As your app grows, you split the root reducer into smaller reducers independently operating on the different parts of the state tree. This is exactly like how there is just one root component in a React app, but it is composed out of many small components. - -This architecture might seem like a lot for a counter app, but the beauty of this pattern is how well it scales to large and complex apps. It also enables very powerful developer tools, because it is possible to trace every mutation to the action that caused it. You can record user sessions and reproduce them just by replaying every action. - -### Redux Toolkit Example - -Redux Toolkit simplifies the process of writing Redux logic and setting up the store. With Redux Toolkit, that same logic looks like: +Redux Toolkit simplifies the process of writing Redux logic and setting up the store. With Redux Toolkit, the basic app logic looks like: ```js import { createSlice, configureStore } from '@reduxjs/toolkit' @@ -199,48 +121,7 @@ store.dispatch(decremented()) // {value: 1} ``` -Redux Toolkit allows us to write shorter logic that's easier to read, while still following the same Redux behavior and data flow. - -## Examples - -Almost all examples have a corresponding CodeSandbox sandbox. This is an interactive version of the code that you can play with online. - -- [**Counter Vanilla**](https://redux.js.org/introduction/examples#counter-vanilla): [Source](https://github.com/reduxjs/redux/tree/master/examples/counter-vanilla) -- [**Counter**](https://redux.js.org/introduction/examples#counter): [Source](https://github.com/reduxjs/redux/tree/master/examples/counter) | [Sandbox](https://codesandbox.io/s/github/reduxjs/redux/tree/master/examples/counter) -- [**Todos**](https://redux.js.org/introduction/examples#todos): [Source](https://github.com/reduxjs/redux/tree/master/examples/todos) | [Sandbox](https://codesandbox.io/s/github/reduxjs/redux/tree/master/examples/todos) -- [**Todos with Undo**](https://redux.js.org/introduction/examples#todos-with-undo): [Source](https://github.com/reduxjs/redux/tree/master/examples/todos-with-undo) | [Sandbox](https://codesandbox.io/s/github/reduxjs/redux/tree/master/examples/todos-with-undo) -- [**TodoMVC**](https://redux.js.org/introduction/examples#todomvc): [Source](https://github.com/reduxjs/redux/tree/master/examples/todomvc) | [Sandbox](https://codesandbox.io/s/github/reduxjs/redux/tree/master/examples/todomvc) -- [**Shopping Cart**](https://redux.js.org/introduction/examples#shopping-cart): [Source](https://github.com/reduxjs/redux/tree/master/examples/shopping-cart) | [Sandbox](https://codesandbox.io/s/github/reduxjs/redux/tree/master/examples/shopping-cart) -- [**Tree View**](https://redux.js.org/introduction/examples#tree-view): [Source](https://github.com/reduxjs/redux/tree/master/examples/tree-view) | [Sandbox](https://codesandbox.io/s/github/reduxjs/redux/tree/master/examples/tree-view) -- [**Async**](https://redux.js.org/introduction/examples#async): [Source](https://github.com/reduxjs/redux/tree/master/examples/async) | [Sandbox](https://codesandbox.io/s/github/reduxjs/redux/tree/master/examples/async) -- [**Universal**](https://redux.js.org/introduction/examples#universal): [Source](https://github.com/reduxjs/redux/tree/master/examples/universal) -- [**Real World**](https://redux.js.org/introduction/examples#real-world): [Source](https://github.com/reduxjs/redux/tree/master/examples/real-world) | [Sandbox](https://codesandbox.io/s/github/reduxjs/redux/tree/master/examples/real-world) - -## Testimonials - -> [“Love what you're doing with Redux”](https://twitter.com/jingc/status/616608251463909376) -> Jing Chen, creator of Flux - -> [“I asked for comments on Redux in FB's internal JS discussion group, and it was universally praised. Really awesome work.”](https://twitter.com/fisherwebdev/status/616286955693682688) -> Bill Fisher, author of Flux documentation - -> [“It's cool that you are inventing a better Flux by not doing Flux at all.”](https://twitter.com/andrestaltz/status/616271392930201604) -> AndrĂ© Staltz, creator of Cycle - -## Thanks - -- [The Elm Architecture](https://github.com/evancz/elm-architecture-tutorial) for a great intro to modeling state updates with reducers; -- [Turning the database inside-out](https://www.confluent.io/blog/turning-the-database-inside-out-with-apache-samza/) for blowing my mind; -- [Developing ClojureScript with Figwheel](https://www.youtube.com/watch?v=j-kj2qwJa_E) for convincing me that re-evaluation should “just work”; -- [Webpack](https://webpack.js.org/concepts/hot-module-replacement/) for Hot Module Replacement; -- [Flummox](https://github.com/acdlite/flummox) for teaching me to approach Flux without boilerplate or singletons; -- [disto](https://github.com/threepointone/disto) for a proof of concept of hot reloadable Stores; -- [NuclearJS](https://github.com/optimizely/nuclear-js) for proving this architecture can be performant; -- [Om](https://github.com/omcljs/om) for popularizing the idea of a single state atom; -- [Cycle](https://github.com/cyclejs/cycle-core) for showing how often a function is the best tool; -- [React](https://github.com/facebook/react) for the pragmatic innovation. - -Special thanks to [Jamie Paton](https://jdpaton.github.io) for handing over the `redux` NPM package name. +Redux Toolkit allows us to write shorter logic that's easier to read, while still following the original core Redux behavior and data flow. ## Logo @@ -251,16 +132,6 @@ You can find the official logo [on GitHub](https://github.com/reduxjs/redux/tree This project adheres to [Semantic Versioning](https://semver.org/). Every release, along with the migration instructions, is documented on the GitHub [Releases](https://github.com/reduxjs/redux/releases) page. -## Patrons - -The work on Redux was [funded by the community](https://www.patreon.com/reactdx). -Meet some of the outstanding companies that made it possible: - -- [Webflow](https://github.com/webflow) -- [Ximedes](https://www.ximedes.com/) - -[See the full list of Redux patrons](PATRONS.md), as well as the always-growing list of [people and companies that use Redux](https://github.com/reduxjs/redux/issues/310). - ## License [MIT](LICENSE.md) diff --git a/docs/understanding/history-and-design/PriorArt.md b/docs/understanding/history-and-design/PriorArt.md index 7bd4448c37..7fb68f40d1 100644 --- a/docs/understanding/history-and-design/PriorArt.md +++ b/docs/understanding/history-and-design/PriorArt.md @@ -8,6 +8,17 @@ description: 'Introduction > Prior Art: Influences on the design of Redux' Redux has a mixed heritage. It is similar to some patterns and technologies, but is also different from them in important ways. We'll explore some of the similarities and the differences below. +## Developer Experience + +Dan Abramov (author of Redux) wrote Redux while working on his React Europe talk called [“Hot Reloading with Time Travel”](https://www.youtube.com/watch?v=xsSnOQynTHs). His goal was to create a state management library with a minimal API but completely predictable behavior. Redux makes it possible to implement logging, hot reloading, time travel, universal apps, record and replay, without any buy-in from the developer. + +Dan talked about some of his intent and approach in [Changelog episode 187](https://changelog.com/187). + +## Influences + +Redux evolves the ideas of [Flux](https://facebook.github.io/flux/), but avoids its complexity by taking cues from [Elm](https://github.com/evancz/elm-architecture-tutorial/). +Even if you haven't used Flux or Elm, Redux only takes a few minutes to get started with. + ### Flux Redux was inspired by several important qualities of [Flux](https://facebook.github.io/flux/). Like Flux, Redux prescribes that you concentrate your model update logic in a certain layer of your application (“stores” in Flux, “reducers” in Redux). Instead of letting the application code directly mutate the data, both tell you to describe every mutation as a plain object called an “action”. @@ -67,3 +78,39 @@ Similarly, you can compose different asynchronous streams to turn them into acti The question is: do you really need Redux if you already use Rx? Maybe not. It's not hard to [re-implement Redux in Rx](https://github.com/jas-chen/rx-redux). Some say it's a two-liner using Rx `.scan()` method. It may very well be! If you're in doubt, check out the Redux source code (there isn't much going on there), as well as its ecosystem (for example, [the developer tools](https://github.com/reduxjs/redux-devtools)). If you don't care too much about it and want to go with the reactive data flow all the way, you might want to explore something like [Cycle](https://cycle.js.org) instead, or even combine it with Redux. Let us know how it goes! + +## Testimonials + +> [“Love what you're doing with Redux”](https://twitter.com/jingc/status/616608251463909376) +> Jing Chen, creator of Flux + +> [“I asked for comments on Redux in FB's internal JS discussion group, and it was universally praised. Really awesome work.”](https://twitter.com/fisherwebdev/status/616286955693682688) +> Bill Fisher, author of Flux documentation + +> [“It's cool that you are inventing a better Flux by not doing Flux at all.”](https://twitter.com/andrestaltz/status/616271392930201604) +> AndrĂ© Staltz, creator of Cycle + +## Thanks + +- [The Elm Architecture](https://github.com/evancz/elm-architecture-tutorial) for a great intro to modeling state updates with reducers; +- [Turning the database inside-out](https://www.confluent.io/blog/turning-the-database-inside-out-with-apache-samza/) for blowing my mind; +- [Developing ClojureScript with Figwheel](https://www.youtube.com/watch?v=j-kj2qwJa_E) for convincing me that re-evaluation should “just work”; +- [Webpack](https://webpack.js.org/concepts/hot-module-replacement/) for Hot Module Replacement; +- [Flummox](https://github.com/acdlite/flummox) for teaching me to approach Flux without boilerplate or singletons; +- [disto](https://github.com/threepointone/disto) for a proof of concept of hot reloadable Stores; +- [NuclearJS](https://github.com/optimizely/nuclear-js) for proving this architecture can be performant; +- [Om](https://github.com/omcljs/om) for popularizing the idea of a single state atom; +- [Cycle](https://github.com/cyclejs/cycle-core) for showing how often a function is the best tool; +- [React](https://github.com/facebook/react) for the pragmatic innovation. + +Special thanks to [Jamie Paton](https://jdpaton.github.io) for handing over the `redux` NPM package name. + +## Patrons + +The original work on Redux was [funded by the community](https://www.patreon.com/reactdx). +Meet some of the outstanding companies that made it possible: + +- [Webflow](https://github.com/webflow) +- [Ximedes](https://www.ximedes.com/) + +[See the full list of Redux patrons](<[PATRONS.md](https://github.com/reduxjs/redux/blob/master/PATRONS.md)>), as well as the always-growing list of [people and companies that use Redux](https://github.com/reduxjs/redux/issues/310). From 846aee488bf572e6054ee700cf0446487cd47129 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 18:05:37 -0500 Subject: [PATCH 08/16] Copy install steps to README --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 96bee7e914..b4d80cb5a0 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,26 @@ You can use Redux together with [React](https://react.dev), or with any other vi ## 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 + ``` npm install @reduxjs/toolkit react-redux ``` From 1af5e225bd1c2992b67cde84b74182a415c1d280 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 18:22:14 -0500 Subject: [PATCH 09/16] Document that `createStore` is deprecated --- docs/api/api-reference.md | 40 ++++++++++++++++----------------------- docs/api/createStore.md | 34 ++++++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/docs/api/api-reference.md b/docs/api/api-reference.md index 8e554ba58c..87a0b011cd 100644 --- a/docs/api/api-reference.md +++ b/docs/api/api-reference.md @@ -9,7 +9,21 @@ The Redux API surface is tiny. Redux defines a set of contracts for you to imple This section documents the complete Redux API. Keep in mind that Redux is only concerned with managing the state. In a real app, you'll also want to use UI bindings like [react-redux](https://github.com/gaearon/react-redux). -### Top-Level Exports +:::danger + +**The original Redux core `createStore` method is deprecated!** + +`createStore` will continue to work indefinitely, but we discourage direct use of `createStore` or the original `redux` package. + +Instead, you should use [the `configureStore` method](https://redux-toolkit.js.org/api/configureStore) from our official [Redux Toolkit](https://redux-toolkit.js.org) package, which wraps `createStore` to provide a better default setup and configuration approach. You should also use Redux Toolkit's [`createSlice` method](https://redux-toolkit.js.org/api/createSlice) for writing reducer logic. + +Redux Toolkit also re-exports all of the other APIs included in the `redux` package as well. + +See the [**Migrating to Modern Redux** page](../usage/migrating-to-modern-redux.mdx) for details on how to update your existing legacy Redux codebase to use Redux Toolkit. + +::: + +## Top-Level Exports - [createStore(reducer, [preloadedState], [enhancer])](createStore.md) - [combineReducers(reducers)](combineReducers.md) @@ -17,32 +31,10 @@ This section documents the complete Redux API. Keep in mind that Redux is only c - [bindActionCreators(actionCreators, dispatch)](bindActionCreators.md) - [compose(...functions)](compose.md) -### Store API +## Store API - [Store](Store.md) - [getState()](Store.md#getState) - [dispatch(action)](Store.md#dispatchaction) - [subscribe(listener)](Store.md#subscribelistener) - [replaceReducer(nextReducer)](Store.md#replacereducernextreducer) - -### Importing - -Every function described above is a top-level export. You can import any of them like this: - -#### ES6 - -```js -import { createStore } from 'redux' -``` - -#### ES5 (CommonJS) - -```js -var createStore = require('redux').createStore -``` - -#### ES5 (UMD build) - -```js -var createStore = Redux.createStore -``` diff --git a/docs/api/createStore.md b/docs/api/createStore.md index c663130228..741ad668d4 100644 --- a/docs/api/createStore.md +++ b/docs/api/createStore.md @@ -7,12 +7,26 @@ description: 'API > createStore: creating a core Redux store'   -# `createStore(reducer, [preloadedState], [enhancer])` +# `createStore(reducer, preloadedState?, enhancer?)` Creates a Redux [store](Store.md) that holds the complete state tree of your app. There should only be a single store in your app. -#### Arguments +:::danger + +**The original Redux core `createStore` method is deprecated!** + +`createStore` will continue to work indefinitely, but we discourage direct use of `createStore` or the original `redux` package. + +Instead, you should use [the `configureStore` method](https://redux-toolkit.js.org/api/configureStore) from our official [Redux Toolkit](https://redux-toolkit.js.org) package, which wraps `createStore` to provide a better default setup and configuration approach. You should also use Redux Toolkit's [`createSlice` method](https://redux-toolkit.js.org/api/createSlice) for writing reducer logic. + +Redux Toolkit also re-exports all of the other APIs included in the `redux` package as well. + +See the [**Migrating to Modern Redux** page](../usage/migrating-to-modern-redux.mdx) for details on how to update your existing legacy Redux codebase to use Redux Toolkit. + +::: + +## Arguments 1. `reducer` _(Function)_: A [reducing function](../understanding/thinking-in-redux/Glossary.md#reducer) that returns the next [state tree](../understanding/thinking-in-redux/Glossary.md#state), given the current state tree and an [action](../understanding/thinking-in-redux/Glossary.md#action) to handle. @@ -49,7 +63,21 @@ console.log(store.getState()) // [ 'Use Redux', 'Read the docs' ] ``` -#### Tips +## Deprecation and Alternate `legacy_createStore` Export + +In [Redux 4.2.0, we marked the original `createStore` method as `@deprecated`](https://github.com/reduxjs/redux/releases/tag/v4.2.0). Strictly speaking, **this is _not_ a breaking change**, nor is it new in 5.0, but we're documenting it here for completeness. + +**This deprecation is solely a _visual_ indicator that is meant to encourage users to [migrate their apps from legacy Redux patterns to use the modern Redux Toolkit APIs](https://redux.js.org/usage/migrating-to-modern-redux)**. The deprecation results in a visual strikethrough when imported and used, like ~~`createStore`~~, but with _no_ runtime errors or warnings. + +**`createStore` will continue to work indefinitely, and will _not_ ever be removed**. But, today we want _all_ Redux users to be using Redux Toolkit for all of their Redux logic. + +To fix this, there are three options: + +- **[Follow our strong suggestion to switch over to Redux Toolkit and `configureStore`](../usage/migrating-to-modern-redux.mdx)** +- Do nothing. It's just a visual strikethrough, and it doesn't affect how your code behaves. Ignore it. +- Switch to using the `legacy_createStore` API that is now exported, which is the exact same function but with no `@deprecated` tag. The simplest option is to do an aliased import rename, like `import { legacy_createStore as createStore } from 'redux'` + +## Tips - Don't create more than one store in an application! Instead, use [`combineReducers`](combineReducers.md) to create a single root reducer out of many. From 20061f4f127fcb4861fea2fe184c711d671fab6b Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 18:58:25 -0500 Subject: [PATCH 10/16] Rework API docs to emphasize use of RTK and nuke outdated references --- docs/api/Store.md | 26 ++++++----------- docs/api/api-reference.md | 11 +++++-- docs/api/applyMiddleware.md | 18 +++++++++--- docs/api/bindActionCreators.md | 20 +++++++------ docs/api/combineReducers.md | 53 ++++++++++++++++++++++++---------- docs/api/compose.md | 18 ++++++++---- 6 files changed, 92 insertions(+), 54 deletions(-) diff --git a/docs/api/Store.md b/docs/api/Store.md index 31b077ff4d..37fcd1e38c 100644 --- a/docs/api/Store.md +++ b/docs/api/Store.md @@ -7,21 +7,11 @@ description: 'API > Store: the core Redux store methods' # Store A store holds the whole [state tree](../understanding/thinking-in-redux/Glossary.md#state) of your application. -The only way to change the state inside it is to dispatch an [action](../understanding/thinking-in-redux/Glossary.md#action) on it. +The only way to change the state inside it is to dispatch an [action](../understanding/thinking-in-redux/Glossary.md#action) on it, which triggers the [root reducer function](../understanding/thinking-in-redux/Glossary.md#reducer) to calculate the new state. A store is not a class. It's just an object with a few methods on it. -To create it, pass your root [reducing function](../understanding/thinking-in-redux/Glossary.md#reducer) to [`createStore`](createStore.md). -> ##### A Note for Flux Users -> -> If you're coming from Flux, there is a single important difference you need to understand. Redux doesn't have a Dispatcher or support many stores. **Instead, there is just a single store with a single root [reducing function](../understanding/thinking-in-redux/Glossary.md#reducer).** As your app grows, instead of adding stores, you split the root reducer into smaller reducers independently operating on the different parts of the state tree. You can use a helper like [`combineReducers`](combineReducers.md) to combine them. This is similar to how there is just one root component in a React app, but it is composed out of many small components. - -### Store Methods - -- [`getState()`](#getstate) -- [`dispatch(action)`](#dispatchaction) -- [`subscribe(listener)`](#subscribelistener) -- [`replaceReducer(nextReducer)`](#replacereducernextreducer) +To create a store, pass your root [reducing function](../understanding/thinking-in-redux/Glossary.md#reducer) to Redux Toolkit's [`configureStore` method](https://redux-toolkit.js.org/api/configureStore), which will set up a Redux store with a good default configuration. (Alternately, if you're not yet using Redux Toolkit, you can use the original [`createStore`](createStore.md) method, but we encourage you to [migrate your code to use Redux Toolkit](../usage/migrating-to-modern-redux.mdx) as soon as possible) ## Store Methods @@ -44,11 +34,13 @@ Dispatches an action. This is the only way to trigger a state change. The store's reducing function will be called with the current [`getState()`](#getState) result and the given `action` synchronously. Its return value will be considered the next state. It will be returned from [`getState()`](#getState) from now on, and the change listeners will immediately be notified. -> ##### A Note for Flux Users -> -> If you attempt to call `dispatch` from inside the [reducer](../understanding/thinking-in-redux/Glossary.md#reducer), it will throw with an error saying “Reducers may not dispatch actions.” This is similar to “Cannot dispatch in a middle of dispatch” error in Flux, but doesn't cause the problems associated with it. In Flux, a dispatch is forbidden while Stores are handling the action and emitting updates. This is unfortunate because it makes it impossible to dispatch actions from component lifecycle hooks or other benign places. -> -> In Redux, subscriptions are called after the root reducer has returned the new state, so you _may_ dispatch in the subscription listeners. You are only disallowed to dispatch inside the reducers because they must have no side effects. If you want to cause a side effect in response to an action, the right place to do this is in the potentially async [action creator](../understanding/thinking-in-redux/Glossary.md#action-creator). +:::caution + +If you attempt to call `dispatch` from inside the [reducer](../understanding/thinking-in-redux/Glossary.md#reducer), it will throw with an error saying "Reducers may not dispatch actions." Reducers are pure functions - they can _only_ return a new state value and must not have side effects (and dispatching is a side effect). + +In Redux, subscriptions are called after the root reducer has returned the new state, so you _may_ dispatch in the subscription listeners. You are only disallowed to dispatch inside the reducers because they must have no side effects. If you want to cause a side effect in response to an action, the right place to do this is in the potentially async [action creator](../understanding/thinking-in-redux/Glossary.md#action-creator). + +::: #### Arguments diff --git a/docs/api/api-reference.md b/docs/api/api-reference.md index 87a0b011cd..43f4bb2dd3 100644 --- a/docs/api/api-reference.md +++ b/docs/api/api-reference.md @@ -5,9 +5,14 @@ title: API Reference # API Reference -The Redux API surface is tiny. Redux defines a set of contracts for you to implement (such as [reducers](../understanding/thinking-in-redux/Glossary.md#reducer)) and provides a few helper functions to tie these contracts together. +This section documents the original Redux core API. The Redux core is small - it defines a set of contracts for you to implement (such as [reducers](../understanding/thinking-in-redux/Glossary.md#reducer)) and provides a few helper functions to tie these contracts together. -This section documents the complete Redux API. Keep in mind that Redux is only concerned with managing the state. In a real app, you'll also want to use UI bindings like [react-redux](https://github.com/gaearon/react-redux). +**In practice, you won't use the Redux core directly**. [**Redux Toolkit**](https://redux-toolkit.js.org) is our official recommended approach for writing Redux logic. It wraps around the Redux core, and contains packages and functions that we think are essential for building a Redux app. Redux Toolkit builds in our suggested best practices, simplifies most Redux tasks, prevents common mistakes, and makes it easier to write Redux applications. Additionally, [**React-Redux**](https://react-redux.js.org) lets your React components talk to the Redux store. + +See their API docs here: + +- https://redux-toolkit.js.org/ +- https://react-redux.js.org/ :::danger @@ -25,7 +30,7 @@ See the [**Migrating to Modern Redux** page](../usage/migrating-to-modern-redux. ## Top-Level Exports -- [createStore(reducer, [preloadedState], [enhancer])](createStore.md) +- [createStore(reducer, preloadedState?, enhancer?)](createStore.md) - [combineReducers(reducers)](combineReducers.md) - [applyMiddleware(...middlewares)](applyMiddleware.md) - [bindActionCreators(actionCreators, dispatch)](bindActionCreators.md) diff --git a/docs/api/applyMiddleware.md b/docs/api/applyMiddleware.md index 777d31bf30..fa69236709 100644 --- a/docs/api/applyMiddleware.md +++ b/docs/api/applyMiddleware.md @@ -9,22 +9,32 @@ description: 'API > applyMiddleware: extending the Redux store' # `applyMiddleware(...middleware)` +## Overview + Middleware is the suggested way to extend Redux with custom functionality. Middleware lets you wrap the store's [`dispatch`](Store.md#dispatchaction) method for fun and profit. The key feature of middleware is that it is composable. Multiple middleware can be combined together, where each middleware requires no knowledge of what comes before or after it in the chain. +:::warning Warning + +You shouldn't have to call `applyMiddleware` directly. Redux Toolkit's [`configureStore` method](https://redux-toolkit.js.org/api/configureStore) automatically adds a default set of middleware to the store, or can accept a list of middleware to add. + +::: + The most common use case for middleware is to support asynchronous actions without much boilerplate code or a dependency on a library like [Rx](https://github.com/Reactive-Extensions/RxJS). It does so by letting you dispatch [async actions](../understanding/thinking-in-redux/Glossary.md#async-action) in addition to normal actions. For example, [redux-thunk](https://github.com/reduxjs/redux-thunk) lets the action creators invert control by dispatching functions. They would receive [`dispatch`](Store.md#dispatchaction) as an argument and may call it asynchronously. Such functions are called _thunks_. Another example of middleware is [redux-promise](https://github.com/acdlite/redux-promise). It lets you dispatch a [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) async action, and dispatches a normal action when the Promise resolves. -Middleware is not baked into [`createStore`](createStore.md) and is not a fundamental part of the Redux architecture, but we consider it useful enough to be supported right in the core. This way, there is a single standard way to extend [`dispatch`](Store.md#dispatchaction) in the ecosystem, and different middleware may compete in expressiveness and utility. +The original Redux [`createStore`](createStore.md) method does not understand what middleware are out of the box - it has to be configured with `applyMiddleware` to add that behavior. However, Redux Toolkit's [`configureStore` method](https://redux-toolkit.js.org/api/configureStore) automatically adds middleware support by default. -#### Arguments +## Arguments - `...middleware` (_arguments_): Functions that conform to the Redux _middleware API_. Each middleware receives [`Store`](Store.md)'s [`dispatch`](Store.md#dispatchaction) and [`getState`](Store.md#getState) functions as named arguments, and returns a function. That function will be given the `next` middleware's dispatch method, and is expected to return a function of `action` calling `next(action)` with a potentially different argument, or at a different time, or maybe not calling it at all. The last middleware in the chain will receive the real store's [`dispatch`](Store.md#dispatchaction) method as the `next` parameter, thus ending the chain. So, the middleware signature is `({ getState, dispatch }) => next => action`. -#### Returns +### Returns (_Function_) A store enhancer that applies the given middleware. The store enhancer signature is `createStore => createStore` but the easiest way to apply it is to pass it to [`createStore()`](./createStore.md) as the last `enhancer` argument. +## Examples + #### Example: Custom Logger Middleware ```js @@ -191,7 +201,7 @@ export default connect(state => ({ }))(SandwichShop) ``` -#### Tips +## Tips - Middleware only wraps the store's [`dispatch`](Store.md#dispatchaction) function. Technically, anything a middleware can do, you can do manually by wrapping every `dispatch` call, but it's easier to manage this in a single place and define action transformations on the scale of the whole project. diff --git a/docs/api/bindActionCreators.md b/docs/api/bindActionCreators.md index e3e5496e82..7579323e64 100644 --- a/docs/api/bindActionCreators.md +++ b/docs/api/bindActionCreators.md @@ -9,6 +9,8 @@ description: 'API > bindActionCreators: wrapping action creators for dispatching # `bindActionCreators(actionCreators, dispatch)` +## Overview + Turns an object whose values are [action creators](../understanding/thinking-in-redux/Glossary.md#action-creator), into an object with the same keys, but with every action creator wrapped into a [`dispatch`](Store.md#dispatchaction) call so they may be invoked directly. Normally you should just call [`dispatch`](Store.md#dispatchaction) directly on your [`Store`](Store.md) instance. If you use Redux with React, [react-redux](https://github.com/gaearon/react-redux) will provide you with the [`dispatch`](Store.md#dispatchaction) function so you can call it directly, too. @@ -17,17 +19,23 @@ The only use case for `bindActionCreators` is when you want to pass some action For convenience, you can also pass an action creator as the first argument, and get a dispatch wrapped function in return. -#### Parameters +:::warning Warning + +This was originally intended for use with the legacy React-Redux `connect` method. It still works, but is rarely needed. + +::: + +## Parameters 1. `actionCreators` (_Function_ or _Object_): An [action creator](../understanding/thinking-in-redux/Glossary.md#action-creator), or an object whose values are action creators. 2. `dispatch` (_Function_): A [`dispatch`](Store.md#dispatchaction) function available on the [`Store`](Store.md) instance. -#### Returns +### Returns (_Function_ or _Object_): An object mimicking the original object, but with each function immediately dispatching the action returned by the corresponding action creator. If you passed a function as `actionCreators`, the return value will also be a single function. -#### Example +## Example #### `TodoActionCreators.js` @@ -103,9 +111,3 @@ function TodoListContainer(props) { export default connect(state => ({ todos: state.todos }))(TodoListContainer) ``` - -#### Tips - -- You might ask: why don't we bind the action creators to the store instance right away, like in classical Flux? The problem is that this won't work well with universal apps that need to render on the server. Most likely you want to have a separate store instance per request so you can prepare them with different data, but binding action creators during their definition means you're stuck with a single store instance for all requests. - -- If you use ES5, instead of `import * as` syntax you can just pass `require('./TodoActionCreators')` to `bindActionCreators` as the first argument. The only thing it cares about is that the values of the `actionCreators` properties are functions. The module system doesn't matter. diff --git a/docs/api/combineReducers.md b/docs/api/combineReducers.md index 424a53a71b..ed3006a685 100644 --- a/docs/api/combineReducers.md +++ b/docs/api/combineReducers.md @@ -9,11 +9,31 @@ description: 'API > combineReducers: merging slice reducers to create combined s # `combineReducers(reducers)` -As your app grows more complex, you'll want to split your [reducing function](../understanding/thinking-in-redux/Glossary.md#reducer) into separate functions, each managing independent parts of the [state](../understanding/thinking-in-redux/Glossary.md#state). +## Overview -The `combineReducers` helper function turns an object whose values are different reducing functions into a single reducing function you can pass to [`createStore`](createStore.md). +The `combineReducers` helper function turns an object whose values are different "slice reducer" functions into a single combined reducer function you can pass to Redux Toolkit's [`configureStore`](https://redux-toolkit.js.org/api/configureStore) (or the legacy [`createStore`](createStore.md) method) + +The resulting combined reducer calls every slice reducer any time an action is dispatched, and gathers their results into a single state object. This enables splitting up reducer logic into separate functions, each managing their own slice of the state independently. + +:::tip + +This should be rarely needed - Redux Toolkit's [`configureStore` method](https://redux-toolkit.js.org/api/configureStore) will automatically call `combineReducers` for you if you pass in an object of slice reducers: + +```ts +const store = configureStore({ + reducer: { + posts: postsReducer, + comments: commentsReducer + } +}) +``` + +You can still call `combineReducers()` yourself if you need to construct the root reducer manually first. + +::: + +### State Slices -The resulting reducer calls every child reducer, and gathers their results into a single state object. **The state produced by `combineReducers()` namespaces the states of each reducer under their keys as passed to `combineReducers()`** Example: @@ -33,23 +53,24 @@ rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer}) You can control state key names by using different keys for the reducers in the passed object. For example, you may call `combineReducers({ todos: myTodosReducer, counter: myCounterReducer })` for the state shape to be `{ todos, counter }`. -A popular convention is to name reducers after the state slices they manage, so you can use ES6 property shorthand notation: `combineReducers({ counter, todos })`. This is equivalent to writing `combineReducers({ counter: counter, todos: todos })`. +## Arguments -> ##### A Note for Flux Users -> -> This function helps you organize your reducers to manage their own slices of state, similar to how you would have different Flux Stores to manage different state. With Redux, there is just one store, but `combineReducers` helps you keep the same logical division between reducers. +1. `reducers` (_Object_): An object whose values correspond to different reducing functions that need to be combined into one. -#### Arguments - -1. `reducers` (_Object_): An object whose values correspond to different reducing functions that need to be combined into one. See the notes below for some rules every passed reducer must follow. +```ts +combineReducers({ + posts: postsReducer, + comments: commentsReducer +}) +``` -> Earlier documentation suggested the use of the ES6 `import * as reducers` syntax to obtain the reducers object. This was the source of a lot of confusion, which is why we now recommend exporting a single reducer obtained using `combineReducers()` from `reducers/index.js` instead. An example is included below. +See the notes below for some rules every passed reducer must follow. -#### Returns +### Returns (_Function_): A reducer that invokes every reducer inside the `reducers` object, and constructs a state object with the same shape. -#### Notes +## Notes This function is mildly opinionated and is skewed towards helping beginners avoid common pitfalls. This is why it attempts to enforce some rules that you don't have to follow if you write the root reducer manually. @@ -63,7 +84,7 @@ Any reducer passed to `combineReducers` must satisfy these rules: While `combineReducers` attempts to check that your reducers conform to some of these rules, you should remember them, and do your best to follow them. `combineReducers` will check your reducers by passing `undefined` to them; this is done even if you specify initial state to `Redux.createStore(combineReducers(...), initialState)`. Therefore, you **must** ensure your reducers work properly when receiving `undefined` as state, even if you never intend for them to actually receive `undefined` in your own code. -#### Example +## Example #### `reducers/todos.js` @@ -130,8 +151,8 @@ console.log(store.getState()) // } ``` -#### Tips +## Tips -- This helper is just a convenience! You can write your own `combineReducers` that [works differently](https://github.com/acdlite/reduce-reducers), or even assemble the state object from the child reducers manually and write a root reducing function explicitly, like you would write any other function. +- This helper is just a convenience! You can write your own `combineReducers` that [works differently](https://github.com/redux-utilities/reduce-reducers), or even assemble the state object from the child reducers manually and write a root reducing function explicitly, like you would write any other function. - You may call `combineReducers` at any level of the reducer hierarchy. It doesn't have to happen at the top. In fact you may use it again to split the child reducers that get too complicated into independent grandchildren, and so on. diff --git a/docs/api/compose.md b/docs/api/compose.md index fc390987c5..476b09fc88 100644 --- a/docs/api/compose.md +++ b/docs/api/compose.md @@ -9,20 +9,28 @@ description: 'API > compose: composing multiple functions together' # `compose(...functions)` +## Overview + Composes functions from right to left. This is a functional programming utility, and is included in Redux as a convenience. -You might want to use it to apply several [store enhancers](../understanding/thinking-in-redux/Glossary.md#store-enhancer) in a row. +You might want to use it to apply several [store enhancers](../understanding/thinking-in-redux/Glossary.md#store-enhancer) in a row. `compose` is also usable as a general-purpose standalone method. + +:::warning Warning + +You shouldn't have to call `compose` directly. Redux Toolkit's [`configureStore` method](https://redux-toolkit.js.org/api/configureStore) automatically configures a Redux store with the standard `applyMiddleware` and Redux DevTools store enhancers, and offers an `enhancers` argument to pass in additional enhancers. + +::: -#### Arguments +## Arguments 1. (_arguments_): The functions to compose. Each function is expected to accept a single parameter. Its return value will be provided as an argument to the function standing to the left, and so on. The exception is the right-most argument which can accept multiple parameters, as it will provide the signature for the resulting composed function. -#### Returns +### Returns (_Function_): The final function obtained by composing the given functions from right to left. -#### Example +## Example This example demonstrates how to use `compose` to enhance a [store](Store.md) with [`applyMiddleware`](applyMiddleware.md) and a few developer tools from the [redux-devtools](https://github.com/reduxjs/redux-devtools) package. @@ -38,6 +46,6 @@ const store = createStore( ) ``` -#### Tips +## Tips - All `compose` does is let you write deeply nested function transformations without the rightward drift of the code. Don't give it too much credit! From 97d0992676207b163fa1feb4e81db137aed53910 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Fri, 24 Nov 2023 19:03:52 -0500 Subject: [PATCH 11/16] ES2015 _all_ the things! --- docs/api/combineReducers.md | 2 +- docs/faq/Actions.md | 4 ++-- docs/faq/General.md | 2 +- docs/faq/ImmutableData.md | 4 ++-- docs/faq/ReactRedux.md | 2 +- docs/introduction/LearningResources.md | 4 ++-- docs/style-guide/style-guide.md | 2 +- docs/tutorials/essentials/part-1-overview-concepts.md | 2 +- .../essentials/part-6-performance-normalization.md | 2 +- docs/tutorials/fundamentals/part-1-overview.md | 2 +- .../fundamentals/part-3-state-actions-reducers.md | 2 +- docs/tutorials/fundamentals/part-4-store.md | 2 +- docs/tutorials/fundamentals/part-8-modern-redux.md | 2 +- docs/understanding/history-and-design/middleware.md | 2 +- docs/usage/ServerRendering.md | 2 +- docs/usage/UsingObjectSpreadOperator.md | 2 +- docs/usage/deriving-data-selectors.md | 4 ++-- docs/usage/nextjs.mdx | 2 +- docs/usage/side-effects-approaches.mdx | 2 +- docs/usage/structuring-reducers/BasicReducerStructure.md | 2 +- docs/usage/structuring-reducers/InitializingState.md | 8 ++++---- docs/usage/structuring-reducers/UsingCombineReducers.md | 8 ++++---- src/bindActionCreators.ts | 2 +- src/combineReducers.ts | 2 +- 24 files changed, 34 insertions(+), 34 deletions(-) diff --git a/docs/api/combineReducers.md b/docs/api/combineReducers.md index ed3006a685..929193b5f0 100644 --- a/docs/api/combineReducers.md +++ b/docs/api/combineReducers.md @@ -80,7 +80,7 @@ Any reducer passed to `combineReducers` must satisfy these rules: - It must never return `undefined`. It is too easy to do this by mistake via an early `return` statement, so `combineReducers` throws if you do that instead of letting the error manifest itself somewhere else. -- If the `state` given to it is `undefined`, it must return the initial state for this specific reducer. According to the previous rule, the initial state must not be `undefined` either. It is handy to specify it with ES6 optional arguments syntax, but you can also explicitly check the first argument for being `undefined`. +- If the `state` given to it is `undefined`, it must return the initial state for this specific reducer. According to the previous rule, the initial state must not be `undefined` either. It is handy to specify it with ES2015 optional arguments syntax, but you can also explicitly check the first argument for being `undefined`. While `combineReducers` attempts to check that your reducers conform to some of these rules, you should remember them, and do your best to follow them. `combineReducers` will check your reducers by passing `undefined` to them; this is done even if you specify initial state to `Redux.createStore(combineReducers(...), initialState)`. Therefore, you **must** ensure your reducers work properly when receiving `undefined` as state, even if you never intend for them to actually receive `undefined` in your own code. diff --git a/docs/faq/Actions.md b/docs/faq/Actions.md index 8ce950fb8a..7768f70b88 100644 --- a/docs/faq/Actions.md +++ b/docs/faq/Actions.md @@ -114,7 +114,7 @@ There are [many async/side effect middlewares available](https://github.com/mark As a general rule of thumb: - Thunks are best for complex synchronous logic (especially code that needs access to the entire Redux store state), and simple async logic (like basic AJAX calls). With the use of `async/await`, it can be reasonable to use thunks for some more complex promise-based logic as well. -- Sagas are best for complex async logic and decoupled "background thread"-type behavior, especially if you need to listen to dispatched actions (which is something that can't be done with thunks). They require familiarity with ES6 generator functions and `redux-saga`'s "effects" operators. +- Sagas are best for complex async logic and decoupled "background thread"-type behavior, especially if you need to listen to dispatched actions (which is something that can't be done with thunks). They require familiarity with ES2015 generator functions and `redux-saga`'s "effects" operators. - Observables solve the same problems as sagas, but rely on RxJS to implement async behavior. They require familiarity with the RxJS API. We recommend that most Redux users should start with thunks, and then add an additional side effect library like sagas or observables later if their app really requires handling for more complex async logic. @@ -131,7 +131,7 @@ Since sagas and observables have the same use case, an application would normall **Discussions** - [Reddit: discussion of using thunks and sagas together, and pros and cons of sagas](https://www.reddit.com/r/reactjs/comments/8vglo0/react_developer_map_by_adamgolab/e1nr597/) -- [Stack Overflow: Pros/cons of using redux-saga with ES6 generators vs redux-thunk with ES2017 async/await](https://stackoverflow.com/questions/34930735/pros-cons-of-using-redux-saga-with-es6-generators-vs-redux-thunk-with-es2017-asy) +- [Stack Overflow: Pros/cons of using redux-saga with ES2015 generators vs redux-thunk with ES2017 async/await](https://stackoverflow.com/questions/34930735/pros-cons-of-using-redux-saga-with-es6-generators-vs-redux-thunk-with-es2017-asy) - [Stack Overflow: Why use Redux-Observable over Redux-Saga?](https://stackoverflow.com/questions/40021344/why-use-redux-observable-over-redux-saga/40027778#40027778) ### Should I dispatch multiple actions in a row from one action creator? diff --git a/docs/faq/General.md b/docs/faq/General.md index 48c8d3761e..744247b168 100644 --- a/docs/faq/General.md +++ b/docs/faq/General.md @@ -104,7 +104,7 @@ Redux can be used as a data store for any UI layer. The most common usage is wit ## Do I need to have a particular build tool to use Redux? -Redux is originally written in ES6 and transpiled for production into ES5 with Webpack and Babel. You should be able to use it regardless of your JavaScript build process. Redux also offers a UMD build that can be used directly without any build process at all. The [counter-vanilla](https://github.com/reduxjs/redux/tree/master/examples/counter-vanilla) example demonstrates basic ES5 usage with Redux included as a `