Type-Safe Asynchronous Actions with Redux Thunk
Provide an example of using Redux Thunk with TypeScript to dispatch type-safe asynchronous actions, ensuring that the resolved data conforms to expected types.
interface User {
id: number;
name: string;
}
interface RootState {
users: User[];
}
// Action types
enum ActionTypes {
FETCH_USERS_REQUEST = 'FETCH_USERS_REQUEST',
FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS',
FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE'
}
// Action creators
const fetchUsersRequest = () => ({
type: ActionTypes.FETCH_USERS_REQUEST
});
const fetchUsersSuccess = (users: User[]) => ({
type: ActionTypes.FETCH_USERS_SUCCESS,
payload: users
});
const fetchUsersFailure = (error: string) => ({
type: ActionTypes.FETCH_USERS_FAILURE,
payload: error
});
Defines the User interface, the RootState, action types, and action creators for fetching users.
import { ThunkAction } from 'redux-thunk';
import { AnyAction } from 'redux';
// Thunk action
export const fetchUsers = (): ThunkAction<void, RootState, unknown, AnyAction> => async (dispatch) => {
dispatch(fetchUsersRequest());
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const data: User[] = await response.json();
dispatch(fetchUsersSuccess(data));
} catch (error) {
dispatch(fetchUsersFailure(error.message));
}
};
Creates a Redux Thunk action that fetches users and dispatches appropriate actions based on the request's outcome.
// rootReducer.ts
import { combineReducers } from 'redux';
const usersReducer = (state: User[] = [], action: AnyAction): User[] => {
switch (action.type) {
case ActionTypes.FETCH_USERS_SUCCESS:
return action.payload;
default:
return state;
}
};
const rootReducer = combineReducers({
users: usersReducer
});
export default rootReducer;
A rootReducer combining all the reducers. Currently including only usersReducer.
// store.ts
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './rootReducer';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
The store file where the Redux store is configured with the thunk middleware.
// index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
index.tsx is the entry point for React, wrapping the App component with Redux's Provider to make the store available to all components.
/* App.tsx */
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUsers } from './actions';
const App: React.FC = () => {
const dispatch = useDispatch();
const users = useSelector((state: RootState) => state.users);
useEffect(() => {
dispatch(fetchUsers());
}, [dispatch]);
// Render user list or loading state
return (
<ul>{ users.map(user => <li key={user.id}>{user.name}</li>) }</ul>
);
};
export default App;
App.tsx is the root React component responsible for dispatching the fetchUsers action on mount and rendering the fetched users.