redux toolkit action type


They extend the store, and allow you to: The most common reason to use middleware is to allow different kinds of async logic to interact with the store. First, createAction overrides the toString() method on the action creators it generates. Each separate type of request needs repeated similar implementation: createAsyncThunk abstracts this pattern by generating the action types and action creators and generating a thunk that dispatches those actions. Redux reducers need to look for specific action types to determine how they should update their state. Because we don't have separate "actions" files, it makes sense to write these thunks directly in our "slice" files. Because both functions need to refer to the same action types, those are usually defined in a third file and imported in both places: The only truly necessary part here is the reducer itself. 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". Let's look at some of the ways that Redux Toolkit can help make your Redux-related code better. // so we explicitly declare the shape of the returned normalized data as a generic arg. // "mutate" the object by overwriting a field, // Can still return an immutably-updated value if we want to, // {type : "ADD_TODO", payload : {text : "Buy milk"}}), // actionCreator.toString() will automatically be called here, // also, if you use TypeScript, the action type will be correctly inferred. That way, they have access to the plain action creators from the slice, and it's easy to find where the thunk function lives. If you need to reuse reducer logic, it is common to write "higher-order reducers" that wrap a reducer function with additional common behavior. Here are some additional details that you might find useful. But, what if you want to have async logic interact with the store by dispatching or checking the current store state? Update its state immutably, by making copies of the parts of the state that need to change and only modifying those copies, The "mutative" code only works correctly inside of our, Immer won't let you mix "mutating" the draft state and also returning a new state value, "Own" a piece of state, including what the initial value is, Define which specific actions result in state updates, We could have written the action types as inline strings in both places, The action creators are good, but they're not, The only reason we're even writing multiple files is because it's common to separate code by what it does, Execute extra logic when any action is dispatched (such as logging the action and state), Pause, modify, delay, replace, or halt dispatched actions. However, there are a couple potential downsides to be aware of when importing and exporting slices. However, the normalizr TS typings currently do not correctly reflect that multiple data types may be included in the results, so you will need to specify that type structure yourself.

If you're already using normalizr or another normalization library, you could consider using it along with createEntityAdapter. The Redux core library is deliberately unopinionated. // First approach: define the initial state using that type, // type SliceState is inferred for the state of the slice, // Or, cast the initial state as necessary. A typical implementation might look like: However, writing code using this approach is tedious. 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. // In this case, `response.data` would be: // [{id: 1, first_name: 'Example', last_name: 'User'}], // { id: 1, first_name: 'Example', last_name: 'UpdatedLastName'}. This is due to the fact that in almost all cases, follow-up generic parameters to createSlice need to be inferred, and TypeScript cannot mix explicit declaration and inference of generic types within the same "generic block". Here's what the hot reloading example might look like using Redux Toolkit: If you provide the middleware argument, configureStore will only use whatever middleware you've listed. // even though action should actually be PayloadAction, TypeScript can't detect that and won't give a warning here. The return type of the payloadCreator will also be reflected in all generated action types. This CodeSandbox example demonstrates the problem: If you encounter this, you may need to restructure your code in a way that avoids the circular references. Also, the action creator overrides toString() so that the action type becomes its string representation. However, like most rules, there are exceptions. * which can sometimes be problematic with yet-unresolved generics. // 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. // Then, handle actions in your reducers: // standard reducer logic, with auto-generated action types per reducer, // Add reducers for additional action types here, and handle loading state as needed, // Later, dispatch the thunk as needed in the app, // reduce the collection by the id property into a shape of { 1: { user }}, // Normalize the data before passing it to our reducer. /** 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. Thunks typically dispatch plain actions, such as dispatch(dataLoaded(response.data)). This makes it effectively impossible to accidentally mutate state in a reducer. The simplest way to use it is to just pass the root reducer function as a parameter named reducer: You can also pass an object full of "slice reducers", and configureStore will call combineReducers for you: Note that this only works for one level of reducers. // typed "fulfilled" or "rejected" action. // 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. If you want to use these from within the payloadCreator, you will need to define some generic arguments, as the types for these arguments cannot be inferred. Keep that in mind as you design your state shape and create your slices. which will result in a Slice. For example, many different slices might want to respond to a "user logged out" action by clearing data or resetting back to initial state values. In development, middleware that check for common mistakes like mutating the state or using non-serializable values. This means that the action creator itself can be used as the "action type" reference in some places, such as the keys provided to builder.addCase or the createReducer object notation. We'll start with a typical "todo list" reducer that uses switch statements and immutable updates: Notice that we specifically call state.concat() to return a copied array with the new todo entry, state.map() to return a copied array for the toggle case, and use the object spread operator to make a copy of the todo that needs to be updated. If you want to have some custom middleware and the defaults all together, you can use the callback notation, This is especially helpful when you are looking to clear persisted state on a dispatched logout action. Like the builder in createReducer, this builder also accepts addMatcher (see typing builder.matcher) and addDefaultCase. However, Redux Toolkit rests on the assumption that you use string action types. This can be done with createSlice as well, but due to the complexity of the types for createSlice, you have to use the SliceCaseReducers and ValidateSliceCaseReducers types in a very specific way. But, we still have to write the action types and the action creators by hand.

So the following two calls would be equivalent: By default, the React Redux useDispatch hook does not contain any types that take middlewares into account. You have to write them separate from the reducer logic, exactly the same as with plain Redux code. This can result in imports being undefined, which will likely break the code that needs that import. The same can be said about rejectValue - if you don't need to access any potential error payload, you can ignore it. The type of the dispatch function type will be directly inferred from the middleware option. In general, any Redux reducer that uses a switch statement can be converted to use createReducer directly. Instead of strings, you could theoretically use numbers, symbols, or anything else (although it's recommended that the value should at least be serializable). 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. See the React Redux documentation for details. If you want to nest reducers, you'll need to call combineReducers yourself to handle the nesting. A typical reducer function needs to: While you can use any conditional logic you want in a reducer, the most common approach is a switch statement, because it's a straightforward way to handle multiple possible values for a single field. If you encounter any problems with the types that are not described on this page, please open an issue for discussion. The basics of using configureStore are shown in TypeScript Quick Start tutorial page.

The Redux docs show an example of writing a function that acts as a lookup table based on action types, but leave it up to users to customize that function themselves. For this reason, we strongly recommend you to only use string action types. Along with this predefined state shape, it generates a set of reducer functions and selectors that know how to work with the data. * This is a general problem when working with immer's Draft type and generics. It takes an action type and returns an action creator for that type. These parameters are typically put in a field called payload, which is part of the Flux Standard Action convention for organizing the contents of action objects. Here's a basic example of how you might normalize the response from a fetchAll API request that returns data in the shape of { users: [{id: 1, first_name: 'normalized', last_name: 'person'}] }, using some hand-written logic: Although we're capable of writing this code, it does become repetitive, especially if you're handling multiple types of data. Unfortunately, the implicit conversion to a string doesn't happen for switch statements. // Note: at the time of writing, normalizr does not automatically infer the result. This is usually not a problem, as these types are only rarely used as literals. This can be very useful for when you want to guarantee a sort order and your data doesn't come presorted. A "start" action is dispatched before the request to indicate that the request is in progress. Immutable update logic, like spreading objects or copying arrays, can probably be converted to direct "mutation". Using this notation with TypeScript looks like this: As TS cannot combine two string literals (slice.name and the key of actionMap) into a new literal, all actionCreators created by createSlice are of type 'string'. Redux Toolkit createAction function uses a couple tricks to make this easier. They take some parameters, and return an action object with a specific type field and the parameters inside the action. 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. 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. 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. This behavior can be particularly useful when used in custom middlewares, where manual casts might be neccessary otherwise. In addition, use of the async/await syntax in thunks makes them easier to read. Or, maybe you're writing a larger application and finding yourself writing some similar code, and you'd like to cut down on how much of that code you have to write by hand. normalizr is a popular existing library for normalizing data.

// 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. In principle, Redux lets you use any kind of value as an action type. Second, JS modules can have "circular reference" problems if two modules try to import each other. // if you type your function argument here, // both `state` and `action` are now correctly typed, // based on the slice state and the `pending` action creator, * If you want to write to values of the state that depend on the generic, * (in this case: `state.data`, which is T), you might need to specify the. To do this, createAction accepts an optional second argument: a "prepare callback" that will be used to construct the payload value. The most common async middleware are: Each of these libraries has different use cases and tradeoffs. It's also fine to keep the immutable updates as-is and return the updated copies, too. In addition, this example only handles loading entries into the state, not updating them. Sometimes we just want the simplest possible way to get started, with some good default behavior out of the box.

// Declare the type your function argument here: // the parameter of `fetchUserById` is automatically inferred to `number` here, // and dispatching the resulting thunkAction will return a Promise of a correctly. This class extends the default JavaScript Array type, only with modified typings for .concat() and the additional .prepend() method. If you want to skip the usage of getDefaultMiddleware altogether, you can still use MiddlewareArray for type-safe concatenation of your middleware array. Every generated actionCreator has a .match(action) method that can be used to determine if the passed action is of the same type as an action that would be created by the action creator. That's where Redux middleware come in. 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. Note: The type field will be added automatically. This allows you to reference the error payload in the reducer as well as in a component after dispatching the createAsyncThunk action. This match method is a TypeScript type guard and can be used to discriminate the payload type of an action. // Or, you can reference the .type field: // if using TypeScript, the action type cannot be inferred that way, // Explicit quotes for the key name, arrow function for the reducer, // Bare key with no quotes, function keyword, // {type : "posts/createPost", payload : {id : 123, title : "Hello World"}}, // Extract the action creators object and the reducer, // Extract and export each action creator by name, // Export the reducer, either as a default or named export, // First, define the reducer and action creators via `createSlice`, // Use a "state machine" approach for loading state instead of booleans, // Destructure and export the plain action creators, // Define a thunk that dispatches those action creators. 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. If you do need to write data fetching logic yourself, we recommend using the Redux Thunk middleware as the standard approach, as it is sufficient for most typical use cases (such as basic AJAX data fetching). As a developer, you are probably most concerned with the actual logic needed to make an API request, what action type names show up in the Redux action history log, and how your reducers should process the fetched data. // returns the string 'Symbol(increment)', // The following case reducer will NOT trigger for, // increment() actions because `increment` will be, // interpreted as a string, rather than being evaluated. The action creator can be called either without arguments or with a payload to be attached to the action. It is recommended to give the type a different name like AppDispatch to prevent confusion, as the type name Dispatch is usually overused. Redux encourages you to write "action creator" functions that encapsulate the process of creating an action object.

The process of setting up middleware and enhancers can be confusing, especially if you're trying to add several pieces of configuration. You can use it on its own without Redux, but it is very commonly used with Redux. In some setups, you will need a literal type for action.type, though. It is not intended to be a complete solution for everything you might want to do with Redux, but it should make a lot of the Redux-related code you need to write a lot simpler (or in some cases, eliminate some of the hand-written code entirely). 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. Action types can just be provided inline: If you have too many case reducers and defining them inline would be messy, or you want to reuse case reducers across slices, you can also define them outside the createSlice call and type them as CaseReducer: You might have noticed that it is not a good idea to pass your SliceState type as a generic to createSlice. This is generally not required though, as you will probably not run into any array-type-widening issues as long as you are using as const and do not use the spread operator. Reducer lookup tables that map an action type string to a reducer function are not easy to fully type correctly. This is good in some cases, because it gives you flexibility, but that flexibility isn't always needed. The serializability dev check middleware will automatically warn anytime it detects non-serializable values in your actions or state. The match method can also be used as a filter method, which makes it powerful when used with redux-observable: Copyright 20152022 Dan Abramov and the Redux documentation authors. Normally, this is done by defining action type strings and action creator functions separately. The repetitive details of defining the multiple action types and dispatching the actions in the right sequence aren't what matters. // Sort by `first_name`. This is typically done by storing collections as objects with the key of an id, while storing a sorted array of those ids. You may also find it to be more convenient to export a hook like useAppDispatch shown below, then using it wherever you'd call useDispatch. createEntityAdapter provides a sortComparer argument that you can leverage to sort the collection of ids in state. The reducer logic clears the loading state in both cases, and either processes the result data from the success case, or stores the error value for potential display. Most of the time, you'll want to define a slice, and export its action creators and reducers. This may be used to track loading state, to allow skipping duplicate requests, or show loading indicators in the UI. The goal of normalizing data is to efficiently organize the data in your state. If you want to get the Dispatch type from your store, you can extract it after creating the store. A typical action creator might look like: Writing action creators by hand can get tedious. This affects both createReducer and the extraReducers argument for createSlice. Configuring this should look something like this: See Redux Toolkit #121: How to use this with Redux-Persist? With createReducer, we can shorten that example considerably: The ability to "mutate" the state is especially helpful when trying to update deeply nested state. 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. Here's some examples of how you can use createReducer. Redux Toolkit's createEntityAdapter API provides a standardized way to store your data in a slice by taking a collection and putting it into the shape of { ids: [], entities: {} }. There may be occasions when you have to deal with actions that need to accept non-serializable data. // Normalize the data so reducers can responded to a predictable payload. This page provides specific details for each of the different APIs included in Redux Toolkit and how to type them correctly with TypeScript. For a more in-depth explanation and further examples, there is a great reference in the Redux docs page on "Normalizing State Shape". `state.ids` would be ordered as. The other common pain points around writing reducers have to do with updating state immutably. This should be done very rarely and only if necessary, and these non-serializable payloads shouldn't ever make it into your application state through a reducer. These three fields (payload, meta and error) adhere to the specification of Flux Standard Actions. The Redux DevTools Extension docs initially suggest using, Having an options object with "named" parameters, which can be easier to read, Letting you provide arrays of middleware and enhancers you want to add to the store, and calling, Enabling the Redux DevTools Extension automatically. // { type: 'counter/increment', payload: 3 }, // returns { type: 'counter/increment', payload: 3 }, // 'The action type is: counter/increment', * createdAt: '2019-10-03T07:53:36.581Z'. It only knows how to synchronously dispatch actions, update the state by calling the root reducer function, and notify the UI that something has changed. As the first matcher argument to builder.addMatcher, a type predicate function should be used. Second, the action type is also defined as a type field on the action creator. However, if you do need to turnoff those warnings, you can customize the middleware by configuring it to ignore specific action types, or fields in actions and state: If using Redux-Persist, you should specifically ignore all the action types it dispatches: Additionally, you can purge any persisted state by adding an extra reducer to the specific slice that you would like to clear when calling persistor.purge(). We encourage you to leave this middleware active to help avoid accidentally making mistakes. and Redux-Persist #988: non-serializable value error for further discussion. 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. // Since we passed in `MyKnownError` to `rejectValue` in `updateUser`, the type information will be available here. This is not the case for non-string action types because toString() will return the string-converted type value rather than the type itself. Also, as TS cannot mix explicit and inferred generic parameters, from this point on you'll have to define the Returned and ThunkArg generic parameter as well. This is particularly useful when a slice reducer needs to handle action types generated by other slices, or generated by specific calls to createAction (such as the actions generated by createAsyncThunk). It lets you decide how you want to handle everything, like store setup, what your state contains, and how you want to build your reducers. It uses the Immer library internally, which lets you write code that "mutates" some data, but actually applies the updates immutably. Specifically in the case of "ducks" or slices, this can occur if slices defined in two different files both want to respond to actions defined in the other file. This will usually require extracting shared code to a separate common file that both modules can import and use. In return, createAsyncThunk will give you a thunk that will take care of dispatching the right actions based on the promise you return, and action types that you can handle in your reducers: The thunk action creator accepts a single argument, which will be passed as the first argument to your payload creator callback.