Francisldn
Posted on November 16, 2022
This article is a continuation of
- React Data Fetching Patterns - useState-useEffect
- React Data Fetching Pattern - Custom Hook using Context API
Click here to access the Github repo for the code examples discussed below.
Redux createAsyncThunk
While React Context API is suitable for smaller apps, we need a more performant solution for larger apps.
Redux provides a solution to the problem of unnecessary re-rendering of components posed by Context API, as Redux only re-renders the updated component. Within the Redux-toolkit
library, one has access to an API known as createAsyncThunk
.
createAsyncThunk
will accept a callback function that returns a promise - this is where the getUsers
API function will reside. Upon execution of the createAsyncThunk
function, similar to a Promise function, it will generate 3 types of actions: pending, fulfilled, rejected, and the users
state will be updated accordingly depending on the action type.
Below is an example of the code:
interface UsersDataState {
users: User[]
loading: boolean
error: string
}
const initialState: UsersDataState = {
users: [] as User[],
loading: false,
error: "",
}
// set up createAsyncThunk function
export const getUsersData = createAsyncThunk("users/fetchUsers", () => {
return getUsers(apiURL).then(({results}) => results)
})
const userSlice = createSlice({
name: "user",
initialState,
reducers: {},
extraReducers: (builder) => {
// Pending - users state is not updated
builder.addCase(getUsersData.pending, (state) => {
state.loading = true
})
// Fulfilled - users state is updated
builder.addCase(getUsersData.fulfilled, (state, action: PayloadAction<UserApi[]>) => {
state.users = action.payload
.map((user: UserApi) => {
return {
id: user.id.value,
firstName: user.name.first,
lastName: user.name.last,
username: user.login.username,
email: user.email,
}
})
state.loading = false
})
// Rejected - users state is an empty array
builder.addCase(getUsersData.rejected, (state, action) => {
state.loading = false
state.users = []
state.error = action.error.message || "Something went wrong"
})
},
})
For a React component to have access to the users
state using Redux, one also has to set up a redux store and then wrap the <App />
component with Redux Provider
. You may refer to the boilerplate code in the repo here for further details.
Having done the above, let's see how we can retrieve the data in the component for UI rendering.
As shown below, within a React component, one has access to 2 redux hooks:
-
useAppSelector
- enable access to states -
useAppDispatch
- enable update to states
We can use useEffect
hook to trigger the data fetching and updating of the users
state. Then the state will be used for UI rendering.
export default function CardList() {
const {users, loading, error} = useAppSelector((state) => state.user)
const dispatch = useAppDispatch()
// update of users state with useEffect
useEffect(() => {
dispatch(getUsersData())
},[])
return (
<div className="grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 justify-center grid-cols-1 mt-8">
{loading
? <Loading />
: error
? <ErrorFallBack />
: users.map((user:User) => (<CardItem key={user.id} user={user}/>))
}
</div>
)
}
Pros
- More Performant than Context API: As Redux avoids unnecessary re-rendering, the app is more performant as a result.
Cons
- Complicated setup: As demonstrated above, there are a lot of boilerplate codes to set up redux slicer and store.
Having discussed a few data fetching methods, could there be a solution that is both simple to set up and performant? Let's proceed to look at the next method - SWR.
To be continued:
For more information on Redux createAsyncThunk
, see here.
If you like the content, please hit the like button so that it can reach more people.
Posted on November 16, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.