In this tutorial, you’ll learn how to manage asynchronous state in your React Native app using Redux Thunk. By the end of today’s lesson, you’ll be able to fetch data from an API, update the Redux state with the response, and handle loading and error states.
What You Will Learn Today:
- Installing and setting up Redux Thunk
- Creating asynchronous actions
- Handling loading and error states
- Displaying data from an API
- Testing asynchronous actions in your app
Step 1: Installing Redux Thunk
To handle asynchronous actions in Redux, you’ll need to install redux-thunk, a middleware that allows you to write action creators that return functions instead of plain objects.
- Install redux-thunk:
npm install redux-thunk
- Modify the Redux store to include the thunk middleware. Open
store/store.js
and update it as follows:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
Explanation:
- redux-thunk: This middleware allows action creators to return functions (which can handle asynchronous operations) instead of plain action objects.
- applyMiddleware(thunk): Adds the thunk middleware to the Redux store, enabling the dispatch of asynchronous actions.
Step 2: Creating Asynchronous Actions with Thunk
Let’s create an action that fetches data from an API. In this example, we’ll fetch a list of posts from a public API and store them in Redux.
- Inside the
store
folder, create a new folder calledactions
and a file calledpostActions.js
:
mkdir store/actions
touch store/actions/postActions.js
- Open
postActions.js
and create the asynchronous action to fetch posts:
export const fetchPosts = () => {
return async (dispatch) => {
dispatch({ type: 'FETCH_POSTS_REQUEST' });
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await response.json();
dispatch({ type: 'FETCH_POSTS_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_POSTS_FAILURE', error: error.message });
}
};
};
Explanation:
- fetchPosts: This action is a thunk because it returns a function instead of a plain object. The thunk function can handle asynchronous operations.
- FETCH_POSTS_REQUEST: Dispatched before the API request starts to indicate that the data is being fetched (used to set a loading state).
- FETCH_POSTS_SUCCESS: Dispatched if the API request is successful, with the response data as the payload.
- FETCH_POSTS_FAILURE: Dispatched if the API request fails, with the error message as the payload.
Step 3: Creating a Reducer to Handle API Responses
Next, we’ll create a reducer to manage the state related to fetching posts, including handling loading and error states.
- Inside the
reducers
folder, create a new file calledpostReducer.js
:
touch store/reducers/postReducer.js
- Open
postReducer.js
and define the reducer:
const initialState = {
posts: [],
loading: false,
error: null,
};
const postReducer = (state = initialState, action) => {
switch (action.type) {
case 'FETCH_POSTS_REQUEST':
return { ...state, loading: true, error: null };
case 'FETCH_POSTS_SUCCESS':
return { ...state, loading: false, posts: action.payload };
case 'FETCH_POSTS_FAILURE':
return { ...state, loading: false, error: action.error };
default:
return state;
}
};
export default postReducer;
Explanation:
- initialState: Contains three properties:
posts
for storing the data,loading
to track if data is being fetched, anderror
for storing error messages. - FETCH_POSTS_REQUEST: Sets
loading
to true when data fetching begins. - FETCH_POSTS_SUCCESS: Updates the
posts
state with the API response and setsloading
to false when the data is successfully fetched. - FETCH_POSTS_FAILURE: Updates the
error
state if the API request fails.
Combine the Reducer
- Open
reducers/index.js
and add thepostReducer
:
import { combineReducers } from 'redux';
import counterReducer from './counterReducer';
import postReducer from './postReducer'; // Add the new post reducer
const rootReducer = combineReducers({
counter: counterReducer,
posts: postReducer, // Combine it with other reducers
});
export default rootReducer;
Step 4: Connecting Redux Thunk to React Components
Let’s create a Posts component that fetches posts and displays them in the UI. We’ll use useDispatch
to trigger the asynchronous action and useSelector
to read the state from Redux.
- Create a new file called
Posts.js
:
touch Posts.js
- Open
Posts.js
and create the component:
import React, { useEffect } from 'react';
import { View, Text, ActivityIndicator, FlatList } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { fetchPosts } from './store/actions/postActions';
export default function Posts() {
const dispatch = useDispatch();
const { posts, loading, error } = useSelector((state) => state.posts);
useEffect(() => {
dispatch(fetchPosts()); // Dispatch the fetchPosts action when the component mounts
}, [dispatch]);
if (loading) {
return <ActivityIndicator size="large" color="#0000ff" />;
}
if (error) {
return (
<View>
<Text>Error: {error}</Text>
</View>
);
}
return (
<FlatList
data={posts}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={{ padding: 10 }}>
<Text style={{ fontWeight: 'bold' }}>{item.title}</Text>
<Text>{item.body}</Text>
</View>
)}
/>
);
}
Explanation:
- useDispatch: Dispatches the
fetchPosts
action to trigger the API request. - useSelector: Retrieves the
posts
,loading
, anderror
states from the Redux store. - useEffect: Calls
fetchPosts
when the component mounts. - ActivityIndicator: Displays a loading spinner while the data is being fetched.
- FlatList: Displays the list of posts once the data is successfully fetched.
Step 5: Integrating the Posts Component into the App
- Open
App.js
and integrate the Posts component:
import * as React from 'react';
import { Provider } from 'react-redux';
import store from './store/store';
import Posts from './Posts';
export default function App() {
return (
<Provider store={store}>
<Posts />
</Provider>
);
}
Explanation:
- We replaced the previous component with Posts, which will now display a list of posts fetched from the API and managed through Redux.
Step 6: Testing Asynchronous Actions
Let’s run the app and test the asynchronous action flow:
- Run your app:
npm start
- In your app, you should see a loading spinner while the data is being fetched from the API. After the data is loaded, a list of posts should be displayed. If there is an error, the error message will be displayed.
Step 7: Recap and Summary
In today’s tutorial, you learned how to manage asynchronous actions using Redux Thunk in a React Native app. Here’s a quick summary of what you’ve done:
- Installed and set up Redux Thunk to handle asynchronous actions in Redux.
- Created an action to fetch data from an API.
- Created reducers to manage the loading, success, and failure states of the API request.
- Connected Redux to React components using
useDispatch
anduseSelector
. - Displayed fetched data and handled loading and error states in the UI.
With Redux Thunk in place, you can now handle asynchronous actions like API calls, ensuring that your app can manage complex state transitions efficiently.
Next Up: Day 4 – Handling Side Effects with Redux-Saga
In Day 4, we’ll explore an alternative way to handle asynchronous operations by using Redux-Saga. Redux-Saga is a powerful tool for managing side effects and making your app more scalable and maintainable.
Stay tuned for more advanced features tomorrow!