// Since our primary key is `idx` and not `id`, // pass in an ID selector to return that field instead. meta may contain extra information about the action, error may contain details about the action failure. // `action` is narrowed down to the type `PayloadAction
` here. This behavior can be particularly useful when used in custom middlewares, where manual casts might be neccessary otherwise. Let's look at some of the ways that Redux Toolkit can help make your Redux-related code better. The same can be said about rejectValue - if you don't need to access any potential error payload, you can ignore it. // typed "fulfilled" or "rejected" action. A helper function for defining a Redux action type and creator. There may be occasions when you have to deal with actions that need to accept non-serializable data. This is usually not a problem, as these types are only rarely used as literals. Redux state is typically organized into "slices", defined by the reducers that are passed to combineReducers: In this example, both users and posts would be considered "slices". Redux-Persist #988: non-serializable value error, Importing or creating the root reducer function, Setting up middleware, likely including at least one middleware to handle asynchronous logic, Possibly altering some of the logic based on whether the application is being built for development or production. Redux encourages you to write "action creator" functions that encapsulate the process of creating an action object. // Normalize the data so reducers can load a predictable payload, like: // `action.payload = { users: {}, articles: {}, comments: {} }`, // Handle the fetch result by inserting the articles here, // And handle the same fetch result by inserting the users here, // Rename the exports for readability in component usage, // In this instance, our user data always has a primary key of `idx`. In general, any Redux reducer that uses a switch statement can be converted to use createReducer directly. Example: Typing createEntityAdapter only requires you to specify the entity type as the single generic argument. The standard approach is to declare an interface or type for your state, create an initial state value that uses that type, and pass the initial state value to createSlice. Unfortunately, the implicit conversion to a string doesn't happen for switch statements. Immutable update logic, like spreading objects or copying arrays, can probably be converted to direct "mutation". // In this instance, our user data always has a primary key of `id`, so we do not need to provide `selectId`. Alternatively, if you choose to not create a rootReducer yourself and instead pass the slice reducers directly to configureStore(), you need to slightly modify the typing to correctly infer the root reducer: If you pass the reducers directly to configureStore() and do not define the root reducer explicitly, there is no reference to rootReducer. Redux Toolkit's RTK Query data fetching API is a purpose built data fetching and caching solution for Redux apps, and can eliminate the need to write any thunks or reducers to manage data fetching. For example, these are all legal ways to define a function inside an object: Using the "object literal function shorthand" is probably the shortest code, but feel free to use whichever of those approaches you want. See the React Redux documentation for details. // `ids: [ 2, 1 ]`, since 'B' comes before 'T'. Redux Toolkit createAction function uses a couple tricks to make this easier. So if you add correctly typed middlewares, dispatch should already be correctly typed. // If you want to track 'loading' or other keys, you would initialize them here: // `getInitialState({ loading: false, activeRequestId: null })`. If you encounter any problems with the types that are not described on this page, please open an issue for discussion. If your data set stores its ID in a different field, you can pass in a selectId argument that returns the appropriate field. In addition, this example only handles loading entries into the state, not updating them. RRF includes timestamp values in most actions and state as of 3.x, but there are PRs that may improve that behavior as of 4.x. If you want to nest reducers, you'll need to call combineReducers yourself to handle the nesting. The goal of normalizing data is to efficiently organize the data in your state. Most of the time, you'll want to define a slice, and export its action creators and reducers. See the TypeScript Quick Start tutorial page for a brief overview of how to set up and use Redux Toolkit and React Redux to work with TypeScript. // By default, `createEntityAdapter` gives you `{ ids: [], entities: {} }`. The easiest way of getting the State type is to define the root reducer in advance and extract its ReturnType. `state.ids` would be ordered as. The usual way to define an action in Redux is to separately declare an action type constant and an action creator function for constructing actions of that type. Consider the other parts: The "ducks" file structure proposes putting all of your Redux-related logic for a given slice into a single file, like this: That simplifies things because we don't need to have multiple files, and we can remove the redundant imports of the action type constants. Thunks typically dispatch plain actions, such as dispatch(dataLoaded(response.data)). As the first matcher argument to builder.addMatcher, a type predicate function should be used. The type of the dispatch function type will be directly inferred from the middleware option. If you need a more specific type for the dispatch function when dispatching, you may specify the type of the returned dispatch function, or create a custom-typed version of useSelector. Here is an example of such a "generic" wrapped createSlice call: In the most common use cases, you should not need to explicitly declare any types for the createAsyncThunk call itself. A possible configuration to work with that behavior could look like: Copyright 20152022 Dan Abramov and the Redux documentation authors. This is not the case for non-string action types because toString() will return the string-converted type value rather than the type itself. This will usually require extracting shared code to a separate common file that both modules can import and use. The basics of using configureStore are shown in TypeScript Quick Start tutorial page. If you want to skip the usage of getDefaultMiddleware altogether, you can still use MiddlewareArray for type-safe concatenation of your middleware array. Like the builder in createReducer, this builder also accepts addMatcher (see typing builder.matcher) and addDefaultCase. If you are using action.type as a discriminator on a discriminated union, for example to correctly type your payload in case statements, you might be interested in this alternative: Created action creators have a match method that acts as a type predicate: This match method is also very useful in combination with redux-observable and RxJS's filter method. This lets you decide how to use these in your own application, whether it be a brand new project or updating a large existing app. Just provide a type for the first argument to the payloadCreator argument as you would for any function argument, and the resulting thunk will accept the same type as its input parameter. This means you don't have to write or use a separate action type variable, or repeat the name and value of an action type like const SOME_ACTION_TYPE = "SOME_ACTION_TYPE". Every Redux app needs to configure and create a Redux store. This match method is a TypeScript type guard and can be used to discriminate the payload type of an action. It uses the Immer library internally, which lets you write code that "mutates" some data, but actually applies the updates immutably. Second, JS modules can have "circular reference" problems if two modules try to import each other. A typical action creator might look like: Writing action creators by hand can get tedious. For this reason, we strongly recommend you to only use string action types. As with the hand-written version, this doesn't handle adding additional entries into the state, or updating them later - it's just loading in everything that was received. Unique action types need to be defined for the three different cases, Each of those action types usually has a corresponding action creator function, A thunk has to be written that dispatches the correct actions in the right sequence. // Export a hook that can be reused to resolve types, // correctly typed middlewares can just be used, // you can also type middlewares manually, // prepend and concat calls can be chained, // action.payload inferred correctly here. We encourage you to leave this middleware active to help avoid accidentally making mistakes. They take some parameters, and return an action object with a specific type field and the parameters inside the action. Normally, this is done by defining action type strings and action creator functions separately. The second argument to the payloadCreator, known as thunkApi, is an object containing references to the dispatch, getState, and extra arguments from the thunk middleware as well as a utility function called rejectWithValue. This means that, for instance, you cannot use a non-string-type action creator as a case reducer key for createReducer(). Most action creators are very simple. The recommended way to do this is using ES6 destructuring and export syntax: You could also just export the slice object itself directly if you prefer. But, what if you want to have async logic interact with the store by dispatching or checking the current store state? To simplify this process, Redux Toolkit includes a createSlice function that will auto-generate the action types and action creators for you, based on the names of the reducer functions you provide. // accessing action.payload would result in an error here, // action.payload can be used as `number` here, // action.payload can be safely used as number here (and will also be correctly inferred by TypeScript), although it's recommended that the value should at least be serializable, Using Prepare Callbacks to Customize Action Contents. So, like with createReducer, you may also use the "builder callback" approach for defining the reducer object argument. A typical slice file that includes thunks would look like this: Data fetching logic for Redux typically follows a predictable pattern: These steps are not required, but are recommended in the Redux tutorials as a suggested pattern. This makes it effectively impossible to accidentally mutate state in a reducer. In this case, you might define some common action types in a separate file using createAction, import those action creators into each slice file, and handle them using the extraReducers argument. Normalizing data doesn't require any special libraries. So, the createPost reducer became an action type of "posts/createPost", and the createPost() action creator will return an action with that type. Redux reducers need to look for specific action types to determine how they should update their state. Redux Toolkit does not currently provide any special APIs or syntax for writing thunk functions. It is also strongly recommended to blacklist any api(s) that you have configured with RTK Query. If you want to add a meta or error property to your action, or customize the payload of your action, you have to use the prepare notation. // When using the provided `selectAll` selector, the result would be sorted: // [{ id: 2, first_name: 'Banana' }, { id: 1, first_name: 'Test' }], // Ignore these field paths in all actions, // just ignore every redux-firebase and react-redux-firebase action type, some hand-written code that checks the global namespace to see if the extension is available, writing a function that acts as a lookup table based on action types, updating nested immutable data by hand is hard, How to fix circular dependency issues in JS, The most common reason to use middleware is to allow different kinds of async logic to interact with the store, Each of these libraries has different use cases and tradeoffs, using the Redux Thunk middleware as the standard approach, automatically sets up the thunk middleware by default, recommended in the Redux tutorials as a suggested pattern, Redux docs page on "Normalizing State Shape", view the full code of this example usage on CodeSandbox, you should not put non-serializable values in state or actions. For most use cases, there is no need to have a literal definition of action.type, so the following can be used: This will result in the created action being of type PayloadActionCreator. The default way of calling createReducer would be with a "lookup table" / "map object", like this: Unfortunately, as the keys are only strings, using that API TypeScript can neither infer nor validate the action types for you: As an alternative, RTK includes a type-safe reducer builder API. /** return type for `thunkApi.getState` */, /** type of the `extra` argument for the thunk middleware, which will be passed in as `thunkApi.extra` */, /** type to be passed into `rejectWithValue`'s first argument that will end up on `rejectedAction.payload` */, /** return type of the `serializeError` option callback */, /** type to be returned from the `getPendingMeta` option callback & merged into `pendingAction.meta` */, /** type to be passed into the second argument of `fulfillWithValue` to finally be merged into `fulfilledAction.meta` */, /** type to be passed into the second argument of `rejectWithValue` to finally be merged into `rejectedAction.meta` */, // Optional fields for defining thunkApi field types, // Return the known error for future handling.