Redux is a package that manages state in React applications. I am assuming you know how to add Redux to a React app. If you don’t know, please check my blog post on how to add Redux to a React app.
Redux helps in streamlining data flow in the app. From event handlers, we create an action using action creators and dispatch the action with data to the store. The store is composed of reducers. These are functions which accepts the previous state and the dispatched action and generates a new state for the store. Before reducers process an action, the action passes through a series of middleware. Redux Saga is one such middleware.
Connect configuration
The CodeSandBox has a simple React app. It fetches a list of users and displays those users in an unordered list. The connect configuration for the component is as follows.
function mapStateToProps(state) { return { users: state.users.list, loading: state.users.loading, error: state.users.error }; } const dispatchProps = { onFetch: fetchUsers }; export default connect( mapStateToProps, dispatchProps )(Main);
From the Main component, we fetch the list of users by dispatching an action to the store.
componentDidMount() { this.props.onFetch(); }
Our action creator, fetchUsers, is quite simple.
export function fetchUsers() { return { type: ActionTypes.FetchUsers }; }
Saga Middleware
The real magic happens in the Saga middleware. We tell the Saga middleware to run a saga.
import { createStore, applyMiddleware } from "redux"; import rootReducer from "./rootReducer"; import createSagaMiddleware from "redux-saga"; import logger from "redux-logger"; import { usersSaga } from "./usersSaga"; const sagaMiddleware = createSagaMiddleware(); const store = createStore(rootReducer, applyMiddleware(sagaMiddleware, logger)); sagaMiddleware.run(usersSaga); export default store;
So, what are these sagas? Saga is a generator function.
export function* usersSaga() { yield takeEvery(ActionTypes.FetchUsers, fetchUsers); }
takeEvery
is a function from Redux Saga. It listens for actions dispatched to the store. If FetchUsers
action is dispatched to the store, the takeEvery function calls the fetchUsers
function. fetchUsers is also a generator function.
function* fetchUsers(action) { yield put({ type: ActionTypes.FetchUsersPending }); try { const response = yield axios.get("https://reqres.in/api/users"); yield put({ type: ActionTypes.FetchUsersFulfilled, list: response.data.data }); } catch (error) { yield put({ type: ActionTypes.FetchUsersRejected, error }); } }
The fetchUsers
starts by dispatching a new action, FetchUsersPending
, to the store. Within Saga, the way to dispatch a function is by using the put
function call. Then the function makes an API call to get a list of users. If the call is successful, the function dispatches a success action with the response data. If there is any exception, the function dispatches a failure action with the error message.
Redux Saga makes use of generator functions. So, the syntax may be a little difficult to understand. After writing a few generator functions, it becomes easy. There are only two functions in Redux Saga that we repeatedly make use of: takeEvery
and put
. takeEvery
function watches for dispatched actions to the store. And if a matching action is found, triggers a generator function. From within a saga, we dispatch new actions using the put
function.