MASIGNCLEAN101

Redux Fetch if 401 Response Then Forward to Login Updated FREE

Redux Fetch if 401 Response Then Forward to Login

Redux Essentials, Office five: Async Logic and Information Fetching

What You'll Acquire

  • How to use the Redux "thunk" middleware for async logic
  • Patterns for handling async request state
  • How to apply the Redux Toolkit createAsyncThunk API to simplify async calls

Prerequisites

  • Familiarity with using AJAX requests to fetch and update data from a server

Introduction

In Part 4: Using Redux Data, we saw how to apply multiple pieces of data from the Redux store within of React components, customize the contents of action objects before they're dispatched, and handle more complex update logic in our reducers.

And so far, all the information we've worked with has been directly within of our React client awarding. However, most real applications demand to piece of work with data from a server, by making HTTP API calls to fetch and save items.

In this section, we'll convert our social media app to fetch the posts and users data from an API, and add together new posts by saving them to the API.

Example Residual API and Client

To keep the instance project isolated but realistic, the initial project setup already included a fake in-memory REST API for our data (configured using the Mock Service Worker mock API tool). The API uses /fakeApi as the base URL for the endpoints, and supports the typical Go/POST/PUT/DELETE HTTP methods for /fakeApi/posts, /fakeApi/users, and fakeApi/notifications. It's defined in src/api/server.js.

The project also includes a modest HTTP API client object that exposes client.get() and customer.post() methods, like to popular HTTP libraries like axios. Information technology's defined in src/api/client.js.

Nosotros'll use the client object to make HTTP calls to our in-memory fake REST API for this section.

Also, the mock server has been set up to reuse the same random seed each fourth dimension the folio is loaded, and so that it will generate the same list of false users and imitation posts. If you lot want to reset that, delete the 'randomTimestampSeed' value in your browser'due south Local Storage and reload the folio, or you lot tin can turn that off by editing src/api/server.js and setting useSeededRNG to false.

info

As a reminder, the code examples focus on the primal concepts and changes for each section. Run into the CodeSandbox projects and the tutorial-steps branch in the project repo for the complete changes in the application.

Thunks and Async Logic

Using Middleware to Enable Async Logic

By itself, a Redux store doesn't know anything about async logic. It only knows how to synchronously dispatch actions, update the land by calling the root reducer function, and notify the UI that something has changed. Any asynchronicity has to happen outside the store.

But, what if you desire to accept async logic interact with the shop by dispatching or checking the current store state? That's where Redux middleware come in. They extend the shop, and allow you to:

  • Execute extra logic when any action is dispatched (such equally logging the action and state)
  • Pause, modify, delay, supervene upon, or halt dispatched actions
  • Write extra code that has access to dispatch and getState
  • Teach dispatch how to accept other values besides patently activity objects, such as functions and promises, past intercepting them and dispatching real action objects instead

The well-nigh common reason to use middleware is to allow different kinds of async logic to interact with the shop. This allows y'all to write code that can dispatch actions and check the store state, while keeping that logic separate from your UI.

There are many kinds of async middleware for Redux, and each lets yous write your logic using different syntax. The most common async middleware is redux-thunk, which lets you write plain functions that may contain async logic directly. Redux Toolkit'southward configureStore function automatically sets up the thunk middleware past default, and we recommend using thunks as the standard arroyo for writing async logic with Redux.

Earlier, nosotros saw what the synchronous data flow for Redux looks like. When nosotros introduce asynchronous logic, we add an extra step where middleware can run logic like AJAX requests, so acceleration actions. That makes the async information flow look like this:

Redux async data flow diagram

Thunk Functions

Once the thunk middleware has been added to the Redux store, it allows you to pass thunk functions directly to store.dispatch. A thunk office will always exist called with (acceleration, getState) as its arguments, and you can apply them inside the thunk equally needed.

Thunks typically dispatch apparently actions using action creators, like dispatch(increase()):

                                          const                                  store                                =                                                configureStore                (                {                                  reducer                :                                  counterReducer                                }                )                                

const exampleThunkFunction = ( acceleration , getState ) => {
const stateBefore = getState ( )
console . log ( ` Counter before: ${ stateBefore . counter } ` )
dispatch ( increase ( ) )
const stateAfter = getState ( )
console . log ( ` Counter after: ${ stateAfter . counter } ` )
}

shop . dispatch ( exampleThunkFunction )

For consistency with dispatching normal activity objects, nosotros typically write these every bit thunk action creators, which render the thunk function. These action creators tin can take arguments that can exist used inside the thunk.

                                          const                                                logAndAdd                                                =                                                amount                                                =>                                                {                                
return ( dispatch , getState ) => {
const stateBefore = getState ( )
console . log ( ` Counter before: ${ stateBefore . counter } ` )
acceleration ( incrementByAmount ( amount ) )
const stateAfter = getState ( )
panel . log ( ` Counter after: ${ stateAfter . counter } ` )
}
}

shop . dispatch ( logAndAdd ( v ) )

Thunks are typically written in "slice" files. createSlice itself does not have any special back up for defining thunks, so yous should write them equally separate functions in the aforementioned piece file. That way, they have admission to the evidently action creators for that slice, and it's easy to find where the thunk lives.

Writing Async Thunks

Thunks may take async logic within of them, such every bit setTimeout, Promises, and async/await. This makes them a good place to put AJAX calls to a server API.

Information fetching logic for Redux typically follows a predictable design:

  • A "start" activeness is dispatched earlier the request, to indicate that the asking is in progress. This may be used to track loading state to allow skipping duplicate requests or show loading indicators in the UI.
  • The async request is made
  • Depending on the request consequence, the async logic dispatches either a "success" action containing the result data, or a "failure" activeness containing error details. The reducer logic clears the loading state in both cases, and either processes the issue data from the success case, or stores the fault value for potential display.

These steps are not required, simply are commonly used. (If all you intendance about is a successful effect, you can but dispatch a unmarried "success" action when the request finishes, and skip the "start" and "failure" deportment.)

Redux Toolkit provides a createAsyncThunk API to implement the creation and dispatching of these actions, and we'll expect at how to use information technology presently.

Detailed Explanation: Dispatching Asking Status Actions in Thunks

If we were to write out the code for a typical async thunk by hand, it might look similar this:

                                                const                                                      getRepoDetailsStarted                                                      =                                                      (                  )                                                      =>                                                      (                  {                                    
type : 'repoDetails/fetchStarted'
} )
const getRepoDetailsSuccess = repoDetails => ( {
blazon : 'repoDetails/fetchSucceeded' ,
payload : repoDetails
} )
const getRepoDetailsFailed = mistake => ( {
type : 'repoDetails/fetchFailed' ,
error
} )
const fetchIssuesCount = ( org , repo ) => async dispatch => {
dispatch ( getRepoDetailsStarted ( ) )
effort {
const repoDetails = await getRepoDetails ( org , repo )
dispatch ( getRepoDetailsSuccess ( repoDetails ) )
} catch ( err ) {
dispatch ( getRepoDetailsFailed ( err . toString ( ) ) )
}
}

Even so, writing lawmaking using this approach is tiresome. Each carve up blazon of asking needs repeated similar implementation:

  • Unique activity types demand to be defined for the three dissimilar cases
  • Each of those activity types usually has a corresponding action creator function
  • A thunk has to be written that dispatches the correct actions in the right sequence

createAsyncThunk abstracts this blueprint by generating the activeness types and action creators, and generating a thunk that dispatches those actions automatically. You lot provide a callback function that makes the async call and returns a Hope with the outcome.


tip

Redux Toolkit has a new RTK Query data fetching API. RTK Query 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. We encourage y'all to attempt it out and see if it can help simplify the data fetching code in your own apps!

We'll comprehend how to use RTK Query starting in Part 7: RTK Query Basics.

Loading Posts

So far, our postsSlice has used some hardcoded sample data equally its initial state. We're going to switch that to start with an empty array of posts instead, and so fetch a list of posts from the server.

In social club to do that, nosotros're going to have to change the structure of the country in our postsSlice, so that we can keep track of the current state of the API request.

Right now, the postsSlice state is a single assortment of posts. Nosotros need to change that to be an object that has the posts array, plus the loading state fields.

Meanwhile, the UI components like <PostsList> are trying to read posts from land.posts in their useSelector hooks, bold that field is an assortment. Nosotros demand to alter those locations also to match the new data.

It would be squeamish if we didn't take to keep rewriting our components every time nosotros made a modify to the information format in our reducers. One way to avoid this is to define reusable selector functions in the slice files, and have the components utilise those selectors to extract the data they need instead of repeating the selector logic in each component. That way, if we do change our country construction again, we merely need to update the lawmaking in the slice file.

The <PostsList> component needs to read a list of all the posts, and the <SinglePostPage> and <EditPostForm> components need to look up a unmarried mail past its ID. Let's export ii small selector functions from postsSlice.js to comprehend those cases:

features/posts/postsSlice.js

                                                const                                      postsSlice                                    =                                                      createSlice                  (                  /* omit piece code*/                  )                                    

export const { postAdded , postUpdated , reactionAdded } = postsSlice . actions

export default postsSlice . reducer

export const selectAllPosts = state => state . posts

export const selectPostById = ( state , postId ) =>
country . posts . find ( post => postal service . id === postId )

Note that the state parameter for these selector functions is the root Redux land object, as information technology was for the inlined bearding selectors we wrote straight inside of useSelector.

We tin can and then utilise them in the components:

features/posts/PostsList.js

                                                // omit imports                                    
import { selectAllPosts } from './postsSlice'

export const PostsList = ( ) => {
const posts = useSelector ( selectAllPosts )
// omit component contents
}

features/posts/SinglePostPage.js

                                                // omit imports                                    
import { selectPostById } from './postsSlice'

export const SinglePostPage = ( { lucifer } ) => {
const { postId } = lucifer . params

const postal service = useSelector ( land => selectPostById ( state , postId ) )
// omit component logic
}

features/posts/EditPostForm.js

                                                // omit imports                                    
import { postUpdated , selectPostById } from './postsSlice'

export const EditPostForm = ( { match } ) => {
const { postId } = lucifer . params

const post = useSelector ( country => selectPostById ( land , postId ) )
// omit component logic
}

It's oftentimes a skillful idea to encapsulate data lookups by writing reusable selectors. You can also create "memoized" selectors that can help amend operation, which nosotros'll look at in a later part of this tutorial.

Simply, like whatsoever abstraction, it's not something you should practice all the fourth dimension, everywhere. Writing selectors means more code to empathize and maintain. Don't experience similar y'all need to write selectors for every single field of your land. Endeavour starting without any selectors, and add some later when you notice yourself looking upward the aforementioned values in many parts of your application code.

Loading State for Requests

When we make an API call, nosotros tin view its progress as a small-scale land machine that can be in one of four possible states:

  • The request hasn't started still
  • The asking is in progress
  • The request succeeded, and we now have the data we need
  • The asking failed, and there'south probably an mistake message

We could track that information using some booleans, similar isLoading: truthful, but it's better to rails these states every bit a unmarried enum value. A good pattern for this is to have a land section that looks like this (using TypeScript blazon notation):

                                          {                                
// Multiple possible status enum values
status : 'idle' | 'loading' | 'succeeded' | 'failed' ,
error : string | zippo
}

These fields would exist alongside whatsoever bodily data is being stored. These specific cord state names aren't required - feel free to use other names if y'all want, similar 'pending' instead of 'loading', or 'consummate' instead of 'succeeded'.

We can use this information to decide what to show in our UI as the request progresses, and also add together logic in our reducers to preclude cases like loading data twice.

Let'southward update our postsSlice to use this pattern to track loading state for a "fetch posts" asking. We'll switch our state from existence an array of posts past itself, to expect like {posts, status, error}. We'll too remove the old sample post entries from our initial state. As office of this change, we also demand to change any uses of state as an assortment to be state.posts instead, because the array is now one level deeper:

features/posts/postsSlice.js

                                                import                                                      {                                      createSlice                  ,                                      nanoid                                    }                                                      from                                                      '@reduxjs/toolkit'                                    

const initialState = {
posts : [ ] ,
condition : 'idle' ,
error : null
}

const postsSlice = createSlice ( {
name : 'posts' ,
initialState ,
reducers : {
postAdded : {
reducer ( land , activity ) {
country . posts . push ( activeness . payload )
} ,
set up ( title , content , userId ) {
// omit fix logic
}
} ,
reactionAdded ( state , action ) {
const { postId , reaction } = activity . payload
const existingPost = country . posts . find ( mail service => post . id === postId )
if ( existingPost ) {
existingPost . reactions [ reaction ] ++
}
} ,
postUpdated ( state , action ) {
const { id , title , content } = action . payload
const existingPost = state . posts . find ( post => mail . id === id )
if ( existingPost ) {
existingPost . championship = title
existingPost . content = content
}
}
}
} )

export const { postAdded , postUpdated , reactionAdded } = postsSlice . actions

consign default postsSlice . reducer

export const selectAllPosts = state => land . posts . posts

consign const selectPostById = ( state , postId ) =>
state . posts . posts . observe ( post => post . id === postId )

Yes, this does mean that we now have a nested object path that looks like country.posts.posts, which is somewhat repetitive and silly :) Nosotros could change the nested array name to be items or data or something if nosotros wanted to avoid that, merely we'll leave information technology as-is for now.

Fetching Information with createAsyncThunk

Redux Toolkit'due south createAsyncThunk API generates thunks that automatically acceleration those "beginning/success/failure" actions for y'all.

Allow'south start by adding a thunk that will brand an AJAX call to retrieve a listing of posts. We'll import the client utility from the src/api folder, and use that to make a request to '/fakeApi/posts'.

features/posts/postsSlice

                                                import                                                      {                                      createSlice                  ,                                      nanoid                  ,                                      createAsyncThunk                                    }                                                      from                                                      '@reduxjs/toolkit'                                    
import { client } from '../../api/client'

const initialState = {
posts : [ ] ,
status : 'idle' ,
error : null
}

export const fetchPosts = createAsyncThunk ( 'posts/fetchPosts' , async ( ) => {
const response = wait client . get ( '/fakeApi/posts' )
return response . data
} )

createAsyncThunk accepts ii arguments:

  • A cord that will be used every bit the prefix for the generated action types
  • A "payload creator" callback function that should render a Promise containing some information, or a rejected Promise with an error

The payload creator will usually make an AJAX call of some kind, and can either return the Promise from the AJAX call directly, or excerpt some information from the API response and return that. We typically write this using the JS async/await syntax, which lets us write functions that utilise Hopes while using standard try/take hold of logic instead of somePromise.then() bondage.

In this case, we pass in 'posts/fetchPosts' equally the action type prefix. Our payload creation callback waits for the API call to render a response. The response object looks similar {data: []}, and we want our dispatched Redux activity to have a payload that is just the array of posts. Then, we extract response.information, and return that from the callback.

If we try calling dispatch(fetchPosts()), the fetchPosts thunk will get-go acceleration an action type of 'posts/fetchPosts/pending':

`createAsyncThunk`: posts pending action

We can mind for this activeness in our reducer and mark the request status equally 'loading'.

One time the Promise resolves, the fetchPosts thunk takes the response.data array we returned from the callback, and dispatches a 'posts/fetchPosts/fulfilled' activeness containing the posts array as activity.payload:

`createAsyncThunk`: posts pending action

Dispatching Thunks from Components

So, permit's update our <PostsList> component to really fetch this data automatically for united states of america.

We'll import the fetchPosts thunk into the component. Similar all of our other action creators, we have to dispatch it, so we'll also need to add the useDispatch hook. Since we desire to fetch this data when <PostsList> mounts, we need to import the React useEffect hook:

features/posts/PostsList.js

                                                import                                                      React                  ,                                                      {                                      useEffect                                    }                                                      from                                                      'react'                                    
import { useSelector , useDispatch } from 'react-redux'
// omit other imports
import { selectAllPosts , fetchPosts } from './postsSlice'

export const PostsList = ( ) => {
const dispatch = useDispatch ( )
const posts = useSelector ( selectAllPosts )

const postStatus = useSelector ( state => land . posts . status )

useEffect ( ( ) => {
if ( postStatus === 'idle' ) {
dispatch ( fetchPosts ( ) )
}
} , [ postStatus , acceleration ] )

// omit rendering logic
}

Information technology's important that we simply try to fetch the list of posts once. If we do information technology every fourth dimension the <PostsList> component renders, or is re-created because we've switched between views, we might end up fetching the posts several times. Nosotros can employ the posts.status enum to help decide if we demand to really start fetching, by selecting that into the component and but starting the fetch if the condition is 'idle'.

Reducers and Loading Actions

Side by side up, we demand to handle both these deportment in our reducers. This requires a bit deeper look at the createSlice API we've been using.

We've already seen that createSlice will generate an action creator for every reducer function we ascertain in the reducers field, and that the generated action types include the proper name of the piece, similar:

                                          panel                .                log                (                                
postUpdated ( { id : '123' , title : 'Starting time Post' , content : 'Some text here' } )
)
/*
{
type: 'posts/postUpdated',
payload: {
id: '123',
championship: 'First Postal service',
content: 'Some text here'
}
}
*/

Still, at that place are times when a slice reducer needs to reply to other actions that weren't defined equally part of this piece'due south reducers field. We can exercise that using the slice extraReducers field instead.

The extraReducers selection should be a function that receives a parameter called builder. The architect object provides methods that let usa define additional case reducers that will run in response to actions defined outside of the slice. We'll use architect.addCase(actionCreator, reducer) to handle each of the deportment dispatched by our async thunks.

Detailed Explanation: Adding Extra Reducers to Slices

The architect object in extraReducers provides methods that let u.s.a. define additional example reducers that will run in response to actions defined outside of the slice:

  • builder.addCase(actionCreator, reducer): defines a case reducer that handles a single known action type based on either an RTK activeness creator or a plain action type string
  • builder.addMatcher(matcher, reducer): defines a case reducer that tin can run in response to any activity where the matcher part returns true
  • builder.addDefaultCase(reducer): defines a case reducer that will run if no other case reducers were executed for this action.

You can chain these together, like builder.addCase().addCase().addMatcher().addDefaultCase(). If multiple matchers friction match the activity, they volition run in the guild they were defined.

                                                import                                                      {                                      increase                                    }                                                      from                                                      '../features/counter/counterSlice'                                    

const postsSlice = createSlice ( {
proper name : 'posts' ,
initialState ,
reducers : {
// slice-specific reducers here
} ,
extraReducers : builder => {
architect
. addCase ( 'counter/decrement' , ( land , activeness ) => { } )
. addCase ( increase , ( state , action ) => { } )
}
} )

If you're using TypeScript, you lot should use the builder callback grade of extraReducers.

Alternately, extraReducers tin also be an object. This is a legacy syntax - it's even so supported, just nosotros recommend the "builder callback" syntax equally it works better with TypeScript.

The keys in the extraReducers object should be Redux activeness type strings, similar 'counter/increment'. We could write those by hand ourselves, although we'd have to quote the keys if they contain any characters similar '/':

                                                const                                      postsSlice                                    =                                                      createSlice                  (                  {                                    
proper name : 'posts' ,
initialState ,
reducers : {
// piece-specific reducers hither
} ,
extraReducers : {
'counter/increment' : ( state , action ) => {
// normal reducer logic to update the posts slice
}
}
} )

Yet, activeness creators generated by Redux Toolkit automatically return their activeness blazon string if you call actionCreator.toString(). This means we tin pass them every bit ES6 object literal computed properties, and the action types volition go the keys of the object:

                                                import                                                      {                                      increment                                    }                                                      from                                                      '../features/counter/counterSlice'                                    
const object = {
[ increment ] : ( ) => { }
}
panel . log ( object )
// { "counter/increment": Function}

This works for the extraReducers field when used equally an object:

                                                import                                                      {                                      increment                                    }                                                      from                                                      '../features/counter/counterSlice'                                    

const postsSlice = createSlice ( {
name : 'posts' ,
initialState ,
reducers : {
// slice-specific reducers here
} ,
extraReducers : {
[ increment ] : ( country , action ) => {
// normal reducer logic to update the posts slice
}
}
} )

Unfortunately, TypeScript fails to recognize this volition work correctly, so you have to use increase.blazon here to laissez passer the type string. It likewise will non correctly infer the type of activity inside the reducer.

In this instance, nosotros need to listen for the "awaiting" and "fulfilled" action types dispatched by our fetchPosts thunk. Those action creators are attached to our actual fetchPost function, and we can laissez passer those to extraReducers to listen for those actions:

                                          export                                                const                                  fetchPosts                                =                                                createAsyncThunk                (                'posts/fetchPosts'                ,                                                async                                                (                )                                                =>                                                {                                
const response = await customer . get ( '/fakeApi/posts' )
render response . data
} )

const postsSlice = createSlice ( {
name : 'posts' ,
initialState ,
reducers : {
// omit existing reducers hither
} ,
extraReducers ( architect ) {
builder
. addCase ( fetchPosts . pending , ( country , activity ) => {
state . status = 'loading'
} )
. addCase ( fetchPosts . fulfilled , ( land , activeness ) => {
state . condition = 'succeeded'
// Add together any fetched posts to the array
state . posts = state . posts . concat ( action . payload )
} )
. addCase ( fetchPosts . rejected , ( state , activity ) => {
state . condition = 'failed'
state . error = activeness . mistake . bulletin
} )
}
} )

We'll handle all three action types that could be dispatched by the thunk, based on the Promise we returned:

  • When the asking starts, we'll fix the status enum to 'loading'
  • If the request succeeds, we mark the status every bit 'succeeded', and add the fetched posts to state.posts
  • If the request fails, we'll mark the condition equally 'failed', and save any error message into the state so we can display it

Displaying Loading Country

Our <PostsList> component is already checking for any updates to the posts that are stored in Redux, and rerendering itself whatsoever time that listing changes. So, if we refresh the page, we should see a random set of posts from our fake API testify up on screen:

The fake API we're using returns data immediately. Nevertheless, a real API phone call will probably take some time to return a response. Information technology's normally a skillful thought to show some kind of "loading..." indicator in the UI so the user knows we're waiting for information.

Nosotros can update our <PostsList> to show a different fleck of UI based on the state.posts.status enum: a spinner if nosotros're loading, an fault message if it failed, or the actual posts list if we accept the information. While we're at it, this is probably a practiced time to extract a <PostExcerpt> component to encapsulate the rendering for one item in the list as well.

The outcome might await similar this:

features/posts/PostsList.js

                                                import                                                      {                                      Spinner                                    }                                                      from                                                      '../../components/Spinner'                                    
import { PostAuthor } from './PostAuthor'
import { TimeAgo } from './TimeAgo'
import { ReactionButtons } from './ReactionButtons'
import { selectAllPosts , fetchPosts } from './postsSlice'

const PostExcerpt = ( { mail service } ) => {
render (
< article className = " post-extract " key = { post . id } >
< h3 > { mail service . championship } </ h3 >
< div >
< PostAuthor userId = { post . user } />
< TimeAgo timestamp = { mail service . date } />
</ div >
< p className = " post-content " > { post . content . substring ( 0 , 100 ) } </ p >

< ReactionButtons post = { mail service } />
< Link to = { ` /posts/ ${ postal service . id } ` } className = " button muted-button " >
View Post
</ Link >
</ article >
)
}

consign const PostsList = ( ) => {
const acceleration = useDispatch ( )
const posts = useSelector ( selectAllPosts )

const postStatus = useSelector ( state => state . posts . status )
const mistake = useSelector ( state => state . posts . mistake )

useEffect ( ( ) => {
if ( postStatus === 'idle' ) {
acceleration ( fetchPosts ( ) )
}
} , [ postStatus , acceleration ] )

let content

if ( postStatus === 'loading' ) {
content = < Spinner text = " Loading... " />
} else if ( postStatus === 'succeeded' ) {
// Sort posts in reverse chronological order by datetime cord
const orderedPosts = posts
. slice ( )
. sort ( ( a , b ) => b . date . localeCompare ( a . engagement ) )

content = orderedPosts . map ( post => (
< PostExcerpt key = { post . id } post = { post } />
) )
} else if ( postStatus === 'failed' ) {
content = < div > { error } </ div >
}

return (
< section className = " posts-list " >
< h2 > Posts </ h2 >
{ content }
</ department >
)
}

You might find that the API calls are taking a while to consummate, and that the loading spinner is staying on screen for a couple seconds. Our mock API server is configured to add a 2-2nd delay to all responses, specifically to help visualize times when there's a loading spinner visible. If you want to modify this behavior, you can open up api/server.js, and alter this line:

api/server.js

                                                // Add an extra filibuster to all endpoints, and so loading spinners show up.                                    
const ARTIFICIAL_DELAY_MS = 2000

Experience free to turn that on and off every bit we get if yous want the API calls to complete faster.

Loading Users

Nosotros're at present fetching and displaying our list of posts. But, if we await at the posts, in that location's a problem: they all at present say "Unknown author" as the authors:

Unknown post authors

This is considering the mail service entries are being randomly generated past the faux API server, which also randomly generates a fix of imitation users every time nosotros reload the page. We demand to update our users piece to fetch those users when the awarding starts.

Like concluding fourth dimension, nosotros'll create some other async thunk to go the users from the API and return them, and so handle the fulfilled action in the extraReducers piece field. We'll skip worrying about loading state for now:

features/users/usersSlice.js

                                                import                                                      {                                      createSlice                  ,                                      createAsyncThunk                                    }                                                      from                                                      '@reduxjs/toolkit'                                    
import { client } from '../../api/customer'

const initialState = [ ]

consign const fetchUsers = createAsyncThunk ( 'users/fetchUsers' , async ( ) => {
const response = await client . go ( '/fakeApi/users' )
render response . data
} )

const usersSlice = createSlice ( {
proper noun : 'users' ,
initialState ,
reducers : { } ,
extraReducers ( architect ) {
builder . addCase ( fetchUsers . fulfilled , ( state , action ) => {
return action . payload
} )
}
} )

export default usersSlice . reducer

Y'all may have noticed that this time the example reducer isn't using the state variable at all. Instead, we're returning the activity.payload directly. Immer lets us update state in two means: either mutating the existing state value, or returning a new upshot. If we return a new value, that volition replace the existing state completely with whatever we return. (Annotation that if you want to manually render a new value, it'due south up to you to write whatever immutable update logic that might be needed.)

In this instance, the initial state was an empty array, and nosotros probably could have done state.push(...action.payload) to mutate it. Just, in our case we really desire to replace the list of users with whatever the server returned, and this avoids any take a chance of accidentally duplicating the list of users in land.

We but need to fetch the list of users once, and nosotros want to do it right when the application starts. We tin exercise that in our index.js file, and directly dispatch the fetchUsers thunk considering we accept the shop right in that location:

index.js

                                                // omit imports                                    

import { fetchUsers } from './features/users/usersSlice'

import { worker } from './api/server'

// Start our mock API server
worker . kickoff ( { onUnhandledRequest : 'bypass' } )

store . dispatch ( fetchUsers ( ) )

ReactDOM . render (
< React . StrictMode >
< Provider store = { store } >
< App / >
< / Provider >
< / React . StrictMode > ,
document . getElementById ( 'root' )
)

Now, each of the posts should exist showing a username again, and we should besides have that same list of users shown in the "Author" dropdown in our <AddPostForm>.

Calculation New Posts

We accept one more than stride for this section. When nosotros add a new post from the <AddPostForm>, that postal service is only getting added to the Redux store inside our app. We need to really brand an API call that volition create the new post entry in our false API server instead, so that it's "saved". (Since this is a fake API, the new mail won't persist if nosotros reload the page, just if we had a existent backend server information technology would be available next time we reload.)

Sending Data with Thunks

Nosotros tin utilize createAsyncThunk to help with sending data, non just fetching information technology. Nosotros'll create a thunk that accepts the values from our <AddPostForm> as an argument, and makes an HTTP POST call to the simulated API to salvage the data.

In the process, we're going to change how nosotros piece of work with the new post object in our reducers. Currently, our postsSlice is creating a new post object in the prepare callback for postAdded, and generating a new unique ID for that post. In most apps that save data to a server, the server will take intendance of generating unique IDs and filling out any extra fields, and will usually return the completed data in its response. And then, we tin can send a request torso similar { title, content, user: userId } to the server, and and then accept the complete postal service object it sends back and add it to our postsSlice state.

features/posts/postsSlice.js

                                                export                                                      const                                      addNewPost                                    =                                                      createAsyncThunk                  (                                    
'posts/addNewPost' ,
// The payload creator receives the fractional `{title, content, user}` object
async initialPost => {
// We send the initial information to the fake API server
const response = await client . mail ( '/fakeApi/posts' , initialPost )
// The response includes the complete post object, including unique ID
return response . data
}
)

const postsSlice = createSlice ( {
name : 'posts' ,
initialState ,
reducers : {
// The existing `postAdded` reducer and prepare callback were deleted
reactionAdded ( country , action ) { } , // omit logic
postUpdated ( state , action ) { } // omit logic
} ,
extraReducers ( architect ) {
// omit posts loading reducers
architect . addCase ( addNewPost . fulfilled , ( state , action ) => {
// We tin direct add together the new post object to our posts array
country . posts . push button ( action . payload )
} )
}
} )

Checking Thunk Results in Components

Finally, we'll update <AddPostForm> to dispatch the addNewPost thunk instead of the old postAdded action. Since this is another API call to the server, it will have some time and could fail. The addNewPost() thunk will automatically dispatch its awaiting/fulfilled/rejected actions to the Redux store, which we're already handling. Nosotros could rail the request status in postsSlice using a second loading enum if we wanted to, just for this example let's go along the loading land tracking limited to the component.

It would be good if we can at least disable the "Save Post" push button while we're waiting for the request, so the user can't accidentally endeavour to salve a mail twice. If the asking fails, nosotros might also want to show an error message here in the form, or perhaps just log it to the console.

We tin can have our component logic await for the async thunk to cease, and check the effect when it's done:

features/posts/AddPostForm.js

                                                import                                                      React                  ,                                                      {                                      useState                                    }                                                      from                                                      'react'                                    
import { useDispatch , useSelector } from 'react-redux'

import { addNewPost } from './postsSlice'

export const AddPostForm = ( ) => {
const [ title , setTitle ] = useState ( '' )
const [ content , setContent ] = useState ( '' )
const [ userId , setUserId ] = useState ( '' )
const [ addRequestStatus , setAddRequestStatus ] = useState ( 'idle' )

// omit useSelectors and change handlers

const canSave =
[ title , content , userId ] . every ( Boolean ) && addRequestStatus === 'idle'

const onSavePostClicked = async ( ) => {
if ( canSave ) {
try {
setAddRequestStatus ( 'pending' )
expect dispatch ( addNewPost ( { title , content , user : userId } ) ) . unwrap ( )
setTitle ( '' )
setContent ( '' )
setUserId ( '' )
} catch ( err ) {
console . mistake ( 'Failed to save the post: ' , err )
} finally {
setAddRequestStatus ( 'idle' )
}
}
}

// omit rendering logic
}

Nosotros can add a loading status enum field as a React useState hook, like to how we're tracking loading state in postsSlice for fetching posts. In this case, we just want to know if the request is in progress or not.

When nosotros call acceleration(addNewPost()), the async thunk returns a Promise from dispatch. We can expect that promise here to know when the thunk has finished its request. Only, we don't yet know if that request succeeded or failed.

createAsyncThunk handles any errors internally, so that nosotros don't see any letters almost "rejected Promises" in our logs. It then returns the final action it dispatched: either the fulfilled action if information technology succeeded, or the rejected activeness if it failed.

However, information technology'due south mutual to want to write logic that looks at the success or failure of the actual request that was made. Redux Toolkit adds a .unwrap() function to the returned Hope, which will render a new Promise that either has the bodily activeness.payload value from a fulfilled activity, or throws an error if information technology's the rejected activeness. This lets u.s. handle success and failure in the component using normal attempt/catch logic. So, we'll articulate out the input fields to reset the class if the post was successfully created, and log the fault to the console if it failed.

If you lot desire to see what happens when the addNewPost API call fails, endeavour creating a new postal service where the "Content" field just has the word "error" (without quotes). The server volition see that and send back a failed response, then you lot should see a message logged to the panel.

What Yous've Learned

Async logic and data fetching are always a complex topic. As y'all've seen, Redux Toolkit includes some tools to automate the typical Redux information fetching patterns.

Here's what our app looks like now that nosotros're fetching information from that fake API:

As a reminder, here'due south what nosotros covered in this department:

Summary

  • You can write reusable "selector" functions to encapsulate reading values from the Redux state
    • Selectors are functions that get the Redux state as an statement, and return some data
  • Redux uses plugins chosen "middleware" to enable async logic
    • The standard async middleware is called redux-thunk, which is included in Redux Toolkit
    • Thunk functions receive dispatch and getState every bit arguments, and tin can employ those equally part of async logic
  • You lot can dispatch boosted actions to help track the loading status of an API phone call
    • The typical design is dispatching a "awaiting" action earlier the call, then either a "success" containing the information or a "failure" action containing the mistake
    • Loading state should usually be stored equally an enum, similar 'idle' | 'loading' | 'succeeded' | 'failed'
  • Redux Toolkit has a createAsyncThunk API that dispatches these deportment for you
    • createAsyncThunk accepts a "payload creator" callback that should return a Hope, and generates pending/fulfilled/rejected action types automatically
    • Generated action creators like fetchPosts dispatch those actions based on the Promise you return
    • You tin mind for these activeness types in createSlice using the extraReducers field, and update the state in reducers based on those deportment.
    • Action creators can be used to automatically fill in the keys of the extraReducers object so the piece knows what actions to listen for.
    • Thunks tin return promises. For createAsyncThunk specifically, you lot tin can look dispatch(someThunk()).unwrap() to handle the request success or failure at the component level.

What'south Next?

We've got 1 more than set of topics to encompass the core Redux Toolkit APIs and usage patterns. In Role 6: Performance and Normalizing Data, we'll await at how Redux usage affects React performance, and some ways we can optimize our application for improved performance.

Redux Fetch if 401 Response Then Forward to Login

DOWNLOAD HERE

Source: https://redux.js.org/tutorials/essentials/part-5-async-logic

Posted by: 10news2onlinea.blogspot.com

Share This :