From 1a92641145a9276e49e49740fbc9285eaa4ab680 Mon Sep 17 00:00:00 2001 From: Corbin Crutchley Date: Fri, 6 Sep 2024 15:52:24 -0700 Subject: [PATCH 1/5] chore: intial setup for Website --- docs/introduction/getting-started.md | 150 + docs/tutorials/quick-start.md | 222 + docs/tutorials/typescript.md | 182 + docs/using-angular-redux/accessing-store.md | 164 + .../usage-with-typescript.md | 284 + package.json | 5 +- website/.gitignore | 22 + website/README.md | 198 + website/docusaurus.config.js | 187 + website/package.json | 26 + website/sidebars.js | 30 + website/src/pages/index.js | 193 + website/src/pages/styles.module.css | 89 + website/src/theme/NotFound.js | 59 + website/static/css/404.css | 33 + website/static/css/codeblock.css | 39 + website/static/css/custom.css | 235 + .../img/external-link-square-alt-solid.svg | 1 + website/static/img/favicon/favicon.ico | Bin 0 -> 4286 bytes website/static/img/github-brands.svg | 1 + website/static/img/noun_Box_1664404.svg | 1 + .../static/img/noun_Certificate_1945625.svg | 1 + website/static/img/noun_Check_1870817.svg | 1 + website/static/img/noun_Rocket_1245262.svg | 1 + website/static/img/redux-logo-landscape.png | Bin 0 -> 42008 bytes website/static/img/redux-logo-twitter.png | Bin 0 -> 29689 bytes website/static/img/redux.svg | 1 + website/static/img/redux_white.svg | 1 + website/static/scripts/codeblock.js | 45 + website/static/scripts/monokaiTheme.js | 62 + website/static/scripts/sidebarScroll.js | 19 + yarn.lock | 19169 +++++++++++----- 32 files changed, 16108 insertions(+), 5313 deletions(-) create mode 100644 docs/introduction/getting-started.md create mode 100644 docs/tutorials/quick-start.md create mode 100644 docs/tutorials/typescript.md create mode 100644 docs/using-angular-redux/accessing-store.md create mode 100644 docs/using-angular-redux/usage-with-typescript.md create mode 100644 website/.gitignore create mode 100644 website/README.md create mode 100644 website/docusaurus.config.js create mode 100644 website/package.json create mode 100644 website/sidebars.js create mode 100644 website/src/pages/index.js create mode 100644 website/src/pages/styles.module.css create mode 100644 website/src/theme/NotFound.js create mode 100644 website/static/css/404.css create mode 100644 website/static/css/codeblock.css create mode 100644 website/static/css/custom.css create mode 100644 website/static/img/external-link-square-alt-solid.svg create mode 100644 website/static/img/favicon/favicon.ico create mode 100644 website/static/img/github-brands.svg create mode 100644 website/static/img/noun_Box_1664404.svg create mode 100644 website/static/img/noun_Certificate_1945625.svg create mode 100644 website/static/img/noun_Check_1870817.svg create mode 100644 website/static/img/noun_Rocket_1245262.svg create mode 100644 website/static/img/redux-logo-landscape.png create mode 100644 website/static/img/redux-logo-twitter.png create mode 100644 website/static/img/redux.svg create mode 100644 website/static/img/redux_white.svg create mode 100644 website/static/scripts/codeblock.js create mode 100644 website/static/scripts/monokaiTheme.js create mode 100644 website/static/scripts/sidebarScroll.js diff --git a/docs/introduction/getting-started.md b/docs/introduction/getting-started.md new file mode 100644 index 0000000..9d3c358 --- /dev/null +++ b/docs/introduction/getting-started.md @@ -0,0 +1,150 @@ +--- +id: getting-started +title: Getting Started with Angular Redux +hide_title: true +sidebar_label: Getting Started +description: 'Introduction > Getting Started: First steps with Angular Redux' +--- + +  + +import LiteYouTubeEmbed from 'react-lite-youtube-embed'; +import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css' + +# Getting Started with React Redux + +[React Redux](https://github.com/reduxjs/react-redux) is the official [React](https://react.dev/) UI bindings layer for [Redux](https://redux.js.org/). It lets your React components read data from a Redux store, and dispatch actions to the store to update state. + +## Installation + +React Redux 8.x requires **React 16.8.3 or later** / **React Native 0.59 or later**, in order to make use of React Hooks. + +### Create a React Redux App + +The recommended way to start new apps with React and Redux is by using [our official Redux+TS template for Vite](https://github.com/reduxjs/redux-templates), or by creating a new Next.js project using [Next's `with-redux` template](https://github.com/vercel/next.js/tree/canary/examples/with-redux). + +Both of these already have Redux Toolkit and React-Redux configured appropriately for that build tool, and come with a small example app that demonstrates how to use several of Redux Toolkit's features. + +```bash +# Vite with our Redux+TS template +# (using the `degit` tool to clone and extract the template) +npx degit reduxjs/redux-templates/packages/vite-template-redux my-app + +# Next.js using the `with-redux` template +npx create-next-app --example with-redux my-app +``` + +We do not currently have official React Native templates, but recommend these templates for standard React Native and for Expo: + +- https://github.com/rahsheen/react-native-template-redux-typescript +- https://github.com/rahsheen/expo-template-redux-typescript + +### An Existing React App + +To use React Redux with your React app, install it as a dependency: + +```bash +# If you use npm: +npm install react-redux + +# Or if you use Yarn: +yarn add react-redux +``` + +You'll also need to [install Redux](https://redux.js.org/introduction/installation) and [set up a Redux store](https://redux.js.org/recipes/configuring-your-store/) in your app. + +React-Redux v8 is written in TypeScript, so all types are automatically included. + +## API Overview + +### `Provider` + +React Redux includes a `` component, which makes the Redux store available to the rest of your app: + +```jsx +import React from 'react' +import ReactDOM from 'react-dom/client' + +import { Provider } from 'react-redux' +import store from './store' + +import App from './App' + +// As of React 18 +const root = ReactDOM.createRoot(document.getElementById('root')) +root.render( + + + , +) +``` + +### Hooks + +React Redux provides a pair of custom React hooks that allow your React components to interact with the Redux store. + +`useSelector` reads a value from the store state and subscribes to updates, while `useDispatch` returns the store's `dispatch` method to let you dispatch actions. + +```jsx +import React from 'react' +import { useSelector, useDispatch } from 'react-redux' +import { + decrement, + increment, + incrementByAmount, + incrementAsync, + selectCount, +} from './counterSlice' +import styles from './Counter.module.css' + +export function Counter() { + const count = useSelector(selectCount) + const dispatch = useDispatch() + + return ( +
+
+ + {count} + +
+ {/* omit additional rendering output here */} +
+ ) +} +``` + +## Learning React Redux + +### Learn Modern Redux Livestream + +Redux maintainer Mark Erikson appeared on the "Learn with Jason" show to explain how we recommend using Redux today. The show includes a live-coded example app that shows how to use Redux Toolkit and React-Redux hooks with Typescript, as well as the new RTK Query data fetching APIs. + +See [the "Learn Modern Redux" show notes page](https://www.learnwithjason.dev/let-s-learn-modern-redux) for a transcript and links to the example app source. + + + +## Help and Discussion + +The **[#redux channel](https://discord.gg/0ZcbPKXt5bZ6au5t)** of the **[Reactiflux Discord community](http://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 - come join us! + +You can also ask questions on [Stack Overflow](https://stackoverflow.com) using the **[#redux tag](https://stackoverflow.com/questions/tagged/redux)**. + +## Docs Translations + +- [Portuguese](https://fernandobelotto.github.io/react-redux) diff --git a/docs/tutorials/quick-start.md b/docs/tutorials/quick-start.md new file mode 100644 index 0000000..3f9f5bc --- /dev/null +++ b/docs/tutorials/quick-start.md @@ -0,0 +1,222 @@ +--- +id: quick-start +title: Quick Start +sidebar_label: Quick Start +hide_title: true +--- + +  + +# React Redux Quick Start + +:::tip What You'll Learn + +- How to set up and use Redux Toolkit with React Redux + +::: + +:::info Prerequisites + +- Familiarity with [ES6 syntax and features](https://www.taniarascia.com/es6-syntax-and-feature-overview/) +- Knowledge of React terminology: [JSX](https://react.dev/learn/writing-markup-with-jsx), [State](https://react.dev/learn/state-a-components-memory), [Function Components, Props](https://react.dev/learn/passing-props-to-a-component), and [Hooks](https://react.dev/reference/react#) +- Understanding of [Redux terms and concepts](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow) + +::: + +## Introduction + +Welcome to the React Redux Quick Start tutorial! **This tutorial will briefly introduce you to React Redux and teach you how to start using it correctly**. + +### How to Read This Tutorial + +This page will focus on just how to set up a Redux application with Redux Toolkit and the main APIs you'll use. For explanations of what Redux is, how it works, and full examples of how to use Redux Toolkit, see [the Redux core docs tutorials](https://redux.js.org/tutorials/index). + +For this tutorial, we assume that you're using Redux Toolkit and React Redux together, as that is the standard Redux usage pattern. The examples are based on [a typical Create-React-App folder structure](https://create-react-app.dev/docs/folder-structure) where all the application code is in a `src`, but the patterns can be adapted to whatever project or folder setup you're using. + +The [Redux+JS template for Create-React-App](https://github.com/reduxjs/cra-template-redux) comes with this same project setup already configured. + +## Usage Summary + +### Install Redux Toolkit and React Redux + +Add the Redux Toolkit and React Redux packages to your project: + +```sh +npm install @reduxjs/toolkit react-redux +``` + +### Create a Redux Store + +Create a file named `src/app/store.js`. Import the `configureStore` API from Redux Toolkit. We'll start by creating an empty Redux store, and exporting it: + +```js title="app/store.js" +import { configureStore } from '@reduxjs/toolkit' + +export default configureStore({ + reducer: {}, +}) +``` + +This creates a Redux store, and also automatically configure the Redux DevTools extension so that you can inspect the store while developing. + +### Provide the Redux Store to React + +Once the store is created, we can make it available to our React components by putting a React Redux `` around our application in `src/index.js`. Import the Redux store we just created, put a `` around your ``, and pass the store as a prop: + +```js title="index.js" +import React from 'react' +import ReactDOM from 'react-dom/client' +import './index.css' +import App from './App' +// highlight-start +import store from './app/store' +import { Provider } from 'react-redux' +// highlight-end + +// As of React 18 +const root = ReactDOM.createRoot(document.getElementById('root')) + +root.render( + // highlight-next-line + + + , +) +``` + +### Create a Redux State Slice + +Add a new file named `src/features/counter/counterSlice.js`. In that file, import the `createSlice` API from Redux Toolkit. + +Creating a slice requires a string name to identify the slice, an initial state value, and one or more reducer functions to define how the state can be updated. Once a slice is created, we can export the generated Redux action creators and the reducer function for the whole slice. + +Redux requires that [we write all state updates immutably, by making copies of data and updating the copies](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#immutability). However, Redux Toolkit's `createSlice` and `createReducer` APIs use [Immer](https://immerjs.github.io/immer/) inside to allow us to [write "mutating" update logic that becomes correct immutable updates](https://redux.js.org/tutorials/fundamentals/part-8-modern-redux#immutable-updates-with-immer). + +```js title="features/counter/counterSlice.js" +import { createSlice } from '@reduxjs/toolkit' + +export const counterSlice = createSlice({ + name: 'counter', + initialState: { + value: 0, + }, + reducers: { + increment: (state) => { + // Redux Toolkit allows us to write "mutating" logic in reducers. It + // doesn't actually mutate the state because it uses the Immer library, + // which detects changes to a "draft state" and produces a brand new + // immutable state based off those changes. + // Also, no return statement is required from these functions. + state.value += 1 + }, + decrement: (state) => { + state.value -= 1 + }, + incrementByAmount: (state, action) => { + state.value += action.payload + }, + }, +}) + +// Action creators are generated for each case reducer function +export const { increment, decrement, incrementByAmount } = counterSlice.actions + +export default counterSlice.reducer +``` + +### Add Slice Reducers to the Store + +Next, we need to import the reducer function from the counter slice and add it to our store. By defining a field inside the `reducer` parameter, we tell the store to use this slice reducer function to handle all updates to that state. + +```js title="app/store.js" +import { configureStore } from '@reduxjs/toolkit' +// highlight-next-line +import counterReducer from '../features/counter/counterSlice' + +export default configureStore({ + reducer: { + // highlight-next-line + counter: counterReducer, + }, +}) +``` + +### Use Redux State and Actions in React Components + +Now we can use the React Redux hooks to let React components interact with the Redux store. We can read data from the store with `useSelector`, and dispatch actions using `useDispatch`. Create a `src/features/counter/Counter.js` file with a `` component inside, then import that component into `App.js` and render it inside of ``. + +```jsx title="features/counter/Counter.js" +import React from 'react' +import { useSelector, useDispatch } from 'react-redux' +import { decrement, increment } from './counterSlice' +import styles from './Counter.module.css' + +export function Counter() { + const count = useSelector((state) => state.counter.value) + const dispatch = useDispatch() + + return ( +
+
+ + {count} + +
+
+ ) +} +``` + +Now, any time you click the "Increment" and "Decrement buttons: + +- The corresponding Redux action will be dispatched to the store +- The counter slice reducer will see the actions and update its state +- The `` component will see the new state value from the store and re-render itself with the new data + +## What You've Learned + +That was a brief overview of how to set up and use Redux Toolkit with React. Recapping the details: + +:::tip Summary + +- **Create a Redux store with `configureStore`** + - `configureStore` accepts a `reducer` function as a named argument + - `configureStore` automatically sets up the store with good default settings +- **Provide the Redux store to the React application components** + - Put a React Redux `` component around your `` + - Pass the Redux store as `` +- **Create a Redux "slice" reducer with `createSlice`** + - Call `createSlice` with a string name, an initial state, and named reducer functions + - Reducer functions may "mutate" the state using Immer + - Export the generated slice reducer and action creators +- **Use the React Redux `useSelector/useDispatch` hooks in React components** + - Read data from the store with the `useSelector` hook + - Get the `dispatch` function with the `useDispatch` hook, and dispatch actions as needed + +::: + +### Full Counter App Example + +Here's the complete Counter application as a running CodeSandbox: + + + +## 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 and React Redux do, and how to use it correctly. diff --git a/docs/tutorials/typescript.md b/docs/tutorials/typescript.md new file mode 100644 index 0000000..6f52c5d --- /dev/null +++ b/docs/tutorials/typescript.md @@ -0,0 +1,182 @@ +--- +id: typescript-quick-start +title: TypeScript Quick Start +sidebar_label: TypeScript Quick Start +hide_title: true +--- + +  + +# React Redux TypeScript Quick Start + +:::tip What You'll Learn + +- How to set up and use Redux Toolkit and React Redux with TypeScript + +::: + +:::info Prerequisites + +- Knowledge of React [Hooks](https://react.dev/reference/react#) +- Understanding of [Redux terms and concepts](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow) +- Understanding of TypeScript syntax and concepts + +::: + +## Introduction + +Welcome to the React Redux TypeScript Quick Start tutorial! **This tutorial will briefly show how to use TypeScript with Redux Toolkit and React-Redux**. + +This page focuses on just how to set up the TypeScript aspects . For explanations of what Redux is, how it works, and full examples of how to use Redux, see [the Redux core docs tutorials](https://redux.js.org/tutorials/index). + +[React Redux](https://react-redux.js.org) is also written in TypeScript as of version 8, and also includes its own type definitions. + +The [Redux+TS template for Create-React-App](https://github.com/reduxjs/cra-template-redux-typescript) comes with a working example of these patterns already configured. + +:::info + +The recently updated `@types/react@18` major version has changed component definitions to remove having `children` as a prop by default. This causes errors if you have multiple copies of `@types/react` in your project. To fix this, tell your package manager to resolve `@types/react` to a single version. Details: + +https://github.com/facebook/react/issues/24304#issuecomment-1094565891 + +::: + +## Project Setup + +### Define Root State and Dispatch Types + +[Redux Toolkit's `configureStore` API](https://redux-toolkit.js.org/api/configureStore) should not need any additional typings. You will, however, want to extract the `RootState` type and the `Dispatch` type so that they can be referenced as needed. Inferring these types from the store itself means that they correctly update as you add more state slices or modify middleware settings. + +Since those are types, it's safe to export them directly from your store setup file such as `app/store.ts` and import them directly into other files. + +```ts title="app/store.ts" +import { configureStore } from '@reduxjs/toolkit' +// ... + +const store = configureStore({ + reducer: { + posts: postsReducer, + comments: commentsReducer, + users: usersReducer, + }, +}) + +// highlight-start +// 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 = typeof store.dispatch +// highlight-end +``` + +### Define Typed Hooks + +While it's possible to import the `RootState` and `AppDispatch` types into each component, it's **better to create typed versions of the `useDispatch` and `useSelector` hooks for usage in your application**. This is important for a couple reasons: + +- For `useSelector`, it saves you the need to type `(state: RootState)` every time +- For `useDispatch`, the default `Dispatch` type does not know about thunks. In order to correctly dispatch thunks, you need to use the specific customized `AppDispatch` type from the store that includes the thunk middleware types, and use that with `useDispatch`. Adding a pre-typed `useDispatch` hook keeps you from forgetting to import `AppDispatch` where it's needed. + +Since these are actual variables, not types, it's important to define them in a separate file such as `app/hooks.ts`, not the store setup file. This allows you to import them into any component file that needs to use the hooks, and avoids potential circular import dependency issues. + +```ts title="app/hooks.ts" +import { useDispatch, useSelector } from 'react-redux' +import type { RootState, AppDispatch } from './store' + +// highlight-start +// Use throughout your app instead of plain `useDispatch` and `useSelector` +export const useAppDispatch = useDispatch.withTypes() +export const useAppSelector = useSelector.withTypes() +// highlight-end +``` + +## Application Usage + +### Define Slice State and Action Types + +Each slice file should define a type for its initial state value, so that `createSlice` can correctly infer the type of `state` in each case reducer. + +All generated actions should be defined using the `PayloadAction` type from Redux Toolkit, which takes the type of the `action.payload` field as its generic argument. + +You can safely import the `RootState` type from the store file here. It's a circular import, but the TypeScript compiler can correctly handle that for types. This may be needed for use cases like writing selector functions. + +```ts title="features/counter/counterSlice.ts" +import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import type { RootState } from '../../app/store' + +// highlight-start +// Define a type for the slice state +interface CounterState { + value: number +} + +// Define the initial state using that type +const initialState: CounterState = { + value: 0, +} +// highlight-end + +export const counterSlice = createSlice({ + name: 'counter', + // `createSlice` will infer the state type from the `initialState` argument + initialState, + reducers: { + increment: (state) => { + state.value += 1 + }, + decrement: (state) => { + state.value -= 1 + }, + // highlight-start + // Use the PayloadAction type to declare the contents of `action.payload` + incrementByAmount: (state, action: PayloadAction) => { + // highlight-end + state.value += action.payload + }, + }, +}) + +export const { increment, decrement, incrementByAmount } = counterSlice.actions + +// Other code such as selectors can use the imported `RootState` type +export const selectCount = (state: RootState) => state.counter.value + +export default counterSlice.reducer +``` + +The generated action creators will be correctly typed to accept a `payload` argument based on the `PayloadAction` type you provided for the reducer. For example, `incrementByAmount` requires a `number` as its argument. + +In some cases, [TypeScript may unnecessarily tighten the type of the initial state](https://github.com/reduxjs/redux-toolkit/pull/827). If that happens, you can work around it by casting the initial state using `as`, instead of declaring the type of the variable: + +```ts +// Workaround: cast state instead of declaring variable type +const initialState = { + value: 0, +} as CounterState +``` + +### Use Typed Hooks in Components + +In component files, import the pre-typed hooks instead of the standard hooks from React-Redux. + +```tsx title="features/counter/Counter.tsx" +import React, { useState } from 'react' + +// highlight-next-line +import { useAppSelector, useAppDispatch } from 'app/hooks' + +import { decrement, increment } from './counterSlice' + +export function Counter() { + // highlight-start + // The `state` arg is correctly typed as `RootState` already + const count = useAppSelector((state) => state.counter.value) + const dispatch = useAppDispatch() + // highlight-end + + // omit rendering logic +} +``` + +## What's Next? + +See [the "Usage with TypeScript" page](../using-react-redux/usage-with-typescript.md) for extended details on how to use Redux Toolkit's APIs with TypeScript. diff --git a/docs/using-angular-redux/accessing-store.md b/docs/using-angular-redux/accessing-store.md new file mode 100644 index 0000000..7872646 --- /dev/null +++ b/docs/using-angular-redux/accessing-store.md @@ -0,0 +1,164 @@ +--- +id: accessing-store +title: Accessing the Store +hide_title: true +sidebar_label: Accessing the Store +description: 'Usage > Accessing the Store: techniques for getting the store in your components' +--- + +  + +# Accessing the Store + +React Redux provides APIs that allow your components to dispatch actions and subscribe to data updates from the store. + +As part of that, React Redux abstracts away the details of which store you are using, and the exact details of how that +store interaction is handled. In typical usage, your own components should never need to care about those details, and +won't ever reference the store directly. React Redux also internally handles the details of how the store and state are +propagated to connected components, so that this works as expected by default. + +However, there may be certain use cases where you may need to customize how the store is passed to +connected components, or access the store directly. Here are some examples of how to do this. + +## Understanding Context Usage + +Internally, React Redux uses [React's "context" feature](https://react.dev/learn/passing-data-deeply-with-context) to make the +Redux store accessible to deeply nested connected components. As of React Redux version 6, this is normally handled +by a single default context object instance generated by `React.createContext()`, called `ReactReduxContext`. + +React Redux's `` component uses `` to put the Redux store and internal subscription wrappers into context. `useSelector`, `useDispatch`, and `connect` call `useContext(ReactReduxContext)` to read those values and handle updates. + +## Using the `useStore` Hook + +The [`useStore` hook](../api/hooks.md#useStore) returns the current store instance from the default `ReactReduxContext`. If you truly need to access the store, this is the recommended approach. + +## Providing Custom Context + +Instead of using the default context instance from React Redux, you may supply your own custom context instance. + +```jsx + + + +``` + +If you supply a custom context, React Redux will use that context instance instead of the one it creates and exports by default. + +After you’ve supplied the custom context to ``, you will need to supply this context instance to all of your connected components that are expected to connect to the same store: + +```js +// You can pass the context as an option to connect +export default connect( + mapState, + mapDispatch, + null, + { context: MyContext } +)(MyComponent) + +// or, call connect as normal to start +const ConnectedComponent = connect( + mapState, + mapDispatch +)(MyComponent) + +// Later, pass the custom context as a prop to the connected component + +``` + +The following runtime error occurs when React Redux does not find a store in the context it is looking. For example: + +- You provided a custom context instance to ``, but did not provide the same instance (or did not provide any) to your connected components. +- You provided a custom context to your connected component, but did not provide the same instance (or did not provide any) to ``. + +> Invariant Violation +> +> Could not find "store" in the context of "Connect(MyComponent)". Either wrap the root component in a ``, or pass a custom React context provider to `` and the corresponding React context consumer to Connect(Todo) in connect options. + +### Custom Context and the hooks API + +To access the custom context via the hooks API, you can create custom hooks via the [hook creator functions](../api/hooks.md#custom-context). + +## Multiple Stores + +[Redux was designed to use a single store](https://redux.js.org/api/store#a-note-for-flux-users). +However, if you are in an unavoidable position of needing to use multiple stores, as of v6 you may do so by providing (multiple) custom contexts. +This also provides a natural isolation of the stores as they live in separate context instances. + +```js +// a naive example +const ContextA = React.createContext(null); +const ContextB = React.createContext(null); + +// assuming reducerA and reducerB are proper reducer functions +const storeA = createStore(reducerA); +const storeB = createStore(reducerB); + +// supply the context instances to Provider +function App() { + return ( + + + + + + ); +} + +// fetch the corresponding store with connected components +// you need to use the correct context +connect(mapStateA, null, null, { context: ContextA })(MyComponentA) + +// You may also pass the alternate context instance directly to the connected component instead + + +// it is possible to chain connect() +// in this case MyComponent will receive merged props from both stores +compose( + connect(mapStateA, null, null, { context: ContextA }), + connect(mapStateB, null, null, { context: ContextB }) +)(MyComponent); +``` + +## Using `ReactReduxContext` Directly + +In rare cases, you may need to access the Redux store directly in your own components. This can be done by rendering +the appropriate context consumer yourself, and accessing the `store` field out of the context value. + +:::caution + +This is **_not_ considered part of the React Redux public API, and may break without notice**. We do recognize +that the community has use cases where this is necessary, and will try to make it possible for users to build additional +functionality on top of React Redux, but our specific use of context is considered an implementation detail. +If you have additional use cases that are not sufficiently covered by the current APIs, please file an issue to discuss +possible API improvements. + +::: + +```jsx +import { ReactReduxContext } from 'react-redux' + +// Somewhere inside of a +function MyConnectedComponent() { + // Access the store via the `useContext` hook + const { store } = useContext(ReactReduxContext) + + // alternately, use the render props form of the context + /* + return ( + + {({ store }) => { + // do something useful with the store, like passing it to a child + // component where it can be used in lifecycle methods + }} + + ) + */ +} +``` + +## Further Resources + +- CodeSandbox example: [A reading list app with theme using a separate store](https://codesandbox.io/s/92pm9n2kl4), implemented by providing (multiple) custom context(s). +- Related issues: + - [#1132: Update docs for using a different store key](https://github.com/reduxjs/react-redux/issues/1132) + - [#1126: `` misses state changes that occur between when its constructor runs and when it mounts](https://github.com/reduxjs/react-redux/issues/1126) diff --git a/docs/using-angular-redux/usage-with-typescript.md b/docs/using-angular-redux/usage-with-typescript.md new file mode 100644 index 0000000..1d6eeb8 --- /dev/null +++ b/docs/using-angular-redux/usage-with-typescript.md @@ -0,0 +1,284 @@ +--- +id: usage-with-typescript +title: Usage with TypeScript +hide_title: true +sidebar_label: Usage with TypeScript +description: 'Usage > TypeScript: how to correctly type React Redux APIs' +--- + +  + +# Usage with TypeScript + +As of React-Redux v8, React-Redux is fully written in TypeScript, and the types are included in the published package. The types also export some helpers to make it easier to write typesafe interfaces between your Redux store and your React components. + +:::info + +The recently updated `@types/react@18` major version has changed component definitions to remove having `children` as a prop by default. This causes errors if you have multiple copies of `@types/react` in your project. To fix this, tell your package manager to resolve `@types/react` to a single version. Details: + +https://github.com/facebook/react/issues/24304#issuecomment-1094565891 + +::: + +## Standard Redux Toolkit Project Setup with TypeScript + +We assume that a typical Redux project is using Redux Toolkit and React Redux together. + +[Redux Toolkit](https://redux-toolkit.js.org) (RTK) is the standard approach for writing modern Redux logic. RTK is already written in TypeScript, and its API is designed to provide a good experience for TypeScript usage. + +The [Redux+TS template for Create-React-App](https://github.com/reduxjs/cra-template-redux-typescript) comes with a working example of these patterns already configured. + +### Define Root State and Dispatch Types + +Using [configureStore](https://redux-toolkit.js.org/api/configureStore) should not need any additional typings. You will, however, want to extract the `RootState` type and the `Dispatch` type so that they can be referenced as needed. Inferring these types from the store itself means that they correctly update as you add more state slices or modify middleware settings. + +Since those are types, it's safe to export them directly from your store setup file such as `app/store.ts` and import them directly into other files. + +```ts title="app/store.ts" +import { configureStore } from '@reduxjs/toolkit' +// ... + +const store = configureStore({ + reducer: { + posts: postsReducer, + comments: commentsReducer, + users: usersReducer, + }, +}) + +// highlight-start +// 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 = typeof store.dispatch +// highlight-end +``` + +### Define Typed Hooks + +While it's possible to import the `RootState` and `AppDispatch` types into each component, it's better to **create pre-typed versions of the `useDispatch` and `useSelector` hooks for usage in your application**. This is important for a couple reasons: + +- For `useSelector`, it saves you the need to type `(state: RootState)` every time +- For `useDispatch`, the default `Dispatch` type does not know about thunks or other middleware. In order to correctly dispatch thunks, you need to use the specific customized `AppDispatch` type from the store that includes the thunk middleware types, and use that with `useDispatch`. Adding a pre-typed `useDispatch` hook keeps you from forgetting to import `AppDispatch` where it's needed. + +Since these are actual variables, not types, it's important to define them in a separate file such as `app/hooks.ts`, not the store setup file. This allows you to import them into any component file that needs to use the hooks, and avoids potential circular import dependency issues. + +#### `.withTypes()` + +Previously, the approach for "pre-typing" hooks with your app setting was a little varied. The result would look something like the snippet below: + +```ts title="app/hooks.ts" +import type { TypedUseSelectorHook } from 'react-redux' +import { useDispatch, useSelector, useStore } from 'react-redux' +import type { AppDispatch, AppStore, RootState } 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 +``` + +React Redux v9.1.0 adds a new `.withTypes` method to each of these hooks, analogous to the [`.withTypes`](https://redux-toolkit.js.org/usage/usage-with-typescript#defining-a-pre-typed-createasyncthunk) method found on Redux Toolkit's `createAsyncThunk`. + +The setup now becomes: + +```ts title="app/hooks.ts" +import { useDispatch, useSelector, useStore } from 'react-redux' +import type { AppDispatch, AppStore, RootState } from './store' + +// highlight-start +// Use throughout your app instead of plain `useDispatch` and `useSelector` +export const useAppDispatch = useDispatch.withTypes() +export const useAppSelector = useSelector.withTypes() +export const useAppStore = useStore.withTypes() +// highlight-end +``` + +## Typing Hooks Manually + +We recommend using the pre-typed `useAppSelector` and `useAppDispatch` hooks shown above. If you prefer not to use those, here is how to type the hooks by themselves. + +### Typing the `useSelector` hook + +When writing selector functions for use with `useSelector`, you should explicitly define the type of the `state` parameter. TS should be able to then infer the return type of the selector, which will be reused as the return type of the `useSelector` hook: + +```ts +interface RootState { + isOn: boolean +} + +// TS infers type: (state: RootState) => boolean +const selectIsOn = (state: RootState) => state.isOn + +// TS infers `isOn` is boolean +const isOn = useSelector(selectIsOn) +``` + +This can also be done inline as well: + +```ts +const isOn = useSelector((state: RootState) => state.isOn) +``` + +### Typing the `useDispatch` hook + +By default, the return value of `useDispatch` is the standard `Dispatch` type defined by the Redux core types, so no declarations are needed: + +```ts +const dispatch = useDispatch() +``` + +If you have a customized version of the `Dispatch` type, you may use that type explicitly: + +```ts +// store.ts +export type AppDispatch = typeof store.dispatch + +// MyComponent.tsx +const dispatch: AppDispatch = useDispatch() +``` + +## Typing the `connect` higher order component + +### Inferring The Connected Props Automatically + +`connect` consists of two functions that are called sequentially. The first function accepts `mapState` and `mapDispatch` as arguments, and returns a second function. The second function accepts the component to be wrapped, and returns a new wrapper component that passes down the props from `mapState` and `mapDispatch`. Normally, both functions are called together, like `connect(mapState, mapDispatch)(MyComponent)`. + +The package includes a helper type, `ConnectedProps`, that can extract the return types of `mapStateToProps` and `mapDispatchToProps` from the first function. This means that if you split the `connect` call into two steps, all of the "props from Redux" can be inferred automatically without having to write them by hand. While this approach may feel unusual if you've been using React-Redux for a while, it does simplify the type declarations considerably. + +```ts +import { connect, ConnectedProps } from 'react-redux' + +interface RootState { + isOn: boolean +} + +const mapState = (state: RootState) => ({ + isOn: state.isOn, +}) + +const mapDispatch = { + toggleOn: () => ({ type: 'TOGGLE_IS_ON' }), +} + +const connector = connect(mapState, mapDispatch) + +// The inferred type will look like: +// {isOn: boolean, toggleOn: () => void} +type PropsFromRedux = ConnectedProps +``` + +The return type of `ConnectedProps` can then be used to type your props object. + +```tsx +interface Props extends PropsFromRedux { + backgroundColor: string +} + +const MyComponent = (props: Props) => ( +
+ +
+) + +export default connector(MyComponent) +``` + +Because types can be defined in any order, you can still declare your component before declaring the connector if you want. + +```tsx +// alternately, declare `type Props = PropsFromRedux & {backgroundColor: string}` +interface Props extends PropsFromRedux { + backgroundColor: string; +} + +const MyComponent = (props: Props) => /* same as above */ + +const connector = connect(/* same as above*/) + +type PropsFromRedux = ConnectedProps + +export default connector(MyComponent) +``` + +### Manually Typing `connect` + +The `connect` higher-order component is somewhat complex to type, because there are 3 sources of props: `mapStateToProps`, `mapDispatchToProps`, and props passed in from the parent component. Here's a full example of what it looks like to do that manually. + +```tsx +import { connect } from 'react-redux' + +interface StateProps { + isOn: boolean +} + +interface DispatchProps { + toggleOn: () => void +} + +interface OwnProps { + backgroundColor: string +} + +type Props = StateProps & DispatchProps & OwnProps + +const mapState = (state: RootState) => ({ + isOn: state.isOn, +}) + +const mapDispatch = { + toggleOn: () => ({ type: 'TOGGLE_IS_ON' }), +} + +const MyComponent = (props: Props) => ( +
+ +
+) + +// Typical usage: `connect` is called after the component is defined +export default connect( + mapState, + mapDispatch, +)(MyComponent) +``` + +It is also possible to shorten this somewhat, by inferring the types of `mapState` and `mapDispatch`: + +```ts +const mapState = (state: RootState) => ({ + isOn: state.isOn, +}) + +const mapDispatch = { + toggleOn: () => ({ type: 'TOGGLE_IS_ON' }), +} + +type StateProps = ReturnType +type DispatchProps = typeof mapDispatch + +type Props = StateProps & DispatchProps & OwnProps +``` + +However, inferring the type of `mapDispatch` this way will break if it is defined as an object and also refers to thunks. + +## Recommendations + +The hooks API is generally simpler to use with static types. **If you're looking for the easiest solution for using static types with React-Redux, use the hooks API.** + +If you're using `connect`, **we recommend using the `ConnectedProps` approach for inferring the props from Redux**, as that requires the fewest explicit type declarations. + +## Resources + +For additional information, see these additional resources: + +- [Redux docs: Usage with TypeScript](https://redux.js.org/recipes/usage-with-typescript): Examples of how to use Redux Toolkit, the Redux core, and React Redux with TypeScript +- [Redux Toolkit docs: TypeScript Quick start](https://redux-toolkit.js.org/tutorials/typescript): shows how to use RTK and the React-Redux hooks API with TypeScript +- [React+TypeScript Cheatsheet](https://github.com/typescript-cheatsheets/react-typescript-cheatsheet): a comprehensive guide to using React with TypeScript +- [React + Redux in TypeScript Guide](https://github.com/piotrwitek/react-redux-typescript-guide): extensive information on patterns for using React and Redux with TypeScript diff --git a/package.json b/package.json index 580816e..c52b8d3 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,11 @@ "scripts": { "ng": "ng", "start": "ng serve", + "start:docs": "yarn workspace website run start", "build": "ng build angular-redux && tsc -p projects/angular-redux/tsconfig.schematics.json && yarn build:copy", "build:copy": "cd ./projects/angular-redux/schematics && copyfiles \"**/*.json\" ../../../dist/angular-redux/schematics && copyfiles \"**/*.template\" ../../../dist/angular-redux/schematics", "build:ng": "ng build", + "build:docs": "yarn workspace website run build", "watch": "ng build --watch --configuration development", "test": "yarn test:ng && yarn test:schematics", "test:ng": "ng test", @@ -14,7 +16,8 @@ }, "workspaces": { "packages": [ - "projects/*" + "projects/*", + "website" ] }, "private": true, diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000..9a0a739 --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,22 @@ +# dependencies +/node_modules +/package.lock.json + +# production +/build + +# generated files +.docusaurus/ +website/.docusaurus/ +.cache-loader + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..0566861 --- /dev/null +++ b/website/README.md @@ -0,0 +1,198 @@ +This website was created with [Docusaurus](https://docusaurus.io/). + +# What's In This Document + +- [Get Started in 5 Minutes](#get-started-in-5-minutes) +- [Directory Structure](#directory-structure) +- [Editing Content](#editing-content) +- [Adding Content](#adding-content) +- [Full Documentation](#full-documentation) + +# Get Started in 5 Minutes + +1. Make sure all the dependencies for the website are installed: + +```sh +# Install dependencies +$ yarn +``` + +2. Run your dev server: + +```sh +# Start the site +$ yarn start +``` + +## Directory Structure + +Your project file structure should look something like this + +``` +my-docusaurus/ + docs/ + doc-1.md + doc-2.md + doc-3.md + website/ + blog/ + 2016-3-11-oldest-post.md + 2017-10-24-newest-post.md + core/ + node_modules/ + pages/ + static/ + css/ + img/ + package.json + sidebar.json + siteConfig.js +``` + +# Editing Content + +## Editing an existing docs page + +Edit docs by navigating to `docs/` and editing the corresponding document: + +`docs/doc-to-be-edited.md` + +```markdown +--- +id: page-needs-edit +title: This Doc Needs To Be Edited +--- + +Edit me... +``` + +For more information about docs, click [here](https://docusaurus.io/docs/en/navigation) + +## Editing an existing blog post + +Edit blog posts by navigating to `website/blog` and editing the corresponding post: + +`website/blog/post-to-be-edited.md` + +```markdown +--- +id: post-needs-edit +title: This Blog Post Needs To Be Edited +--- + +Edit me... +``` + +For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) + +# Adding Content + +## Adding a new docs page to an existing sidebar + +1. Create the doc as a new markdown file in `/docs`, example `docs/newly-created-doc.md`: + +```md +--- +id: newly-created-doc +title: This Doc Needs To Be Edited +--- + +My new content here.. +``` + +1. Refer to that doc's ID in an existing sidebar in `website/sidebar.json`: + +```javascript +// Add newly-created-doc to the Getting Started category of docs +{ + "docs": { + "Getting Started": [ + "quick-start", + "newly-created-doc" // new doc here + ], + ... + }, + ... +} +``` + +For more information about adding new docs, click [here](https://docusaurus.io/docs/en/navigation) + +## Adding a new blog post + +1. Make sure there is a header link to your blog in `website/siteConfig.js`: + +`website/siteConfig.js` + +```javascript +headerLinks: [ + ... + { blog: true, label: 'Blog' }, + ... +] +``` + +2. Create the blog post with the format `YYYY-MM-DD-My-Blog-Post-Title.md` in `website/blog`: + +`website/blog/2018-05-21-New-Blog-Post.md` + +```markdown +--- +author: Frank Li +authorURL: https://twitter.com/foobarbaz +authorFBID: 503283835 +title: New Blog Post +--- + +Lorem Ipsum... +``` + +For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) + +## Adding items to your site's top navigation bar + +1. Add links to docs, custom pages or external links by editing the headerLinks field of `website/siteConfig.js`: + +`website/siteConfig.js` + +```javascript +{ + headerLinks: [ + ... + /* you can add docs */ + { doc: 'my-examples', label: 'Examples' }, + /* you can add custom pages */ + { page: 'help', label: 'Help' }, + /* you can add external links */ + { href: 'https://github.com/facebook/Docusaurus', label: 'GitHub' }, + ... + ], + ... +} +``` + +For more information about the navigation bar, click [here](https://docusaurus.io/docs/en/navigation) + +## Adding custom pages + +1. Docusaurus uses React components to build pages. The components are saved as .js files in `website/pages/en`: +1. If you want your page to show up in your navigation header, you will need to update `website/siteConfig.js` to add to the `headerLinks` element: + +`website/siteConfig.js` + +```javascript +{ + headerLinks: [ + ... + { page: 'my-new-custom-page', label: 'My New Custom Page' }, + ... + ], + ... +} +``` + +For more information about custom pages, click [here](https://docusaurus.io/docs/en/custom-pages). + +# Full Documentation + +Full documentation can be found on the [website](https://docusaurus.io/). diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js new file mode 100644 index 0000000..0316d14 --- /dev/null +++ b/website/docusaurus.config.js @@ -0,0 +1,187 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// See https://docusaurus.io/docs/site-config for all the possible +// site configuration options. + +const siteConfig = { + presets: [ + [ + '@docusaurus/preset-classic', + { + docs: { + path: '../docs', + routeBasePath: '/', + sidebarPath: require.resolve('./sidebars.js'), + showLastUpdateTime: true, + editUrl: 'https://github.com/reduxjs/angular-redux/edit/main/website', + include: [ + '{introduction,using-angular-redux,tutorials}/*.{md,mdx}' + ], // no other way to exclude node_modules + }, + theme: { + customCss: [ + require.resolve('./static/css/custom.css'), + require.resolve('./static/css/404.css'), + require.resolve('./static/css/codeblock.css'), + ], + }, + }, + ], + ], + title: 'Angular Redux', // Title for your website. + onBrokenLinks: 'throw', + tagline: 'Official Angular bindings for Redux', + url: 'https://angular-redux.js.org', // Your website URL + baseUrl: '/', + // Used for publishing and more + projectName: 'angular-redux', + organizationName: 'reduxjs', + + // For no header links in the top nav bar -> headerLinks: [], + /* path to images for header/footer */ + favicon: 'img/favicon/favicon.ico', + + // Add custom scripts here that would be placed in