In this part of the tutorial, we will discuss the Redux Saga and its usage in React applications.
The main purpose of the Redux Saga, which acts as middleware, is to take dispatched actions, of specific types, and execute requests to the server, and receive responses.
This tutorial is based on prior knowledge of Actions and their complete flow. If you do not possess this knowledge please refer to this tutorial, before you continue.
In this tutorial, we send JSON objects as requests, and retrieve JSON objects as responses, using http protocol. For binary data transfer, please refer to .
In this example, we will have a look at saving a recipe information in the server database action.
This action is used to upload recipe contents as JSON string from the client to the server and saving them in its database.
We define the complete action flow:
In our case, we will have a look at the SaveRecipe action that is dispatched once the user clicks on the Save button inside AddRecipe.
The flow is implemented as follows:
In the code below, we execute onClickSaveRecipe event handler, for the onClick event of the Save button found in AddRecipe file. We provide the event handler function the recipe contents as JSON object taken from AddRecipe props.
The addRecipePageState object is retrieved from the redux store of the application. The code to retrieve this recipe is found in mapStateToProps function of the AddRecipePage container file.
const AddRecipe = (props) => {
return (
<Button
className={props.classes.rightIcon}
variant="contained"
color="secondary"
onClick={() => {
props.onClickSaveRecipe(props.addRecipePageState);
}}>
<Save className={props.classes.leftIcon}/>
Save
</Button>
)
};
We now implement the onClickSaveRecipe function which will dispatch the saveRecipe action. The code below is part of the mapDispatchToProps arrow function found under AddRecipePage file.
const mapDispatchToProps = (dispatch) => {
return {
onClickSaveRecipe: (recipe) => {
dispatch(recipeActions.saveRecipe(recipe))
},
}
};
We need to implement both the saveRecipe action, and the SAVE_RECIPE action constant. The action is found under actions file, and the relevant constants are found under the constants file.
The saveRecipe contains the following fields:
function saveRecipe(recipe){
return {
type: recipeConstants.SAVE_RECIPE,
url: '/api/save/recipe',
payload: recipe
}
}
Two things are required:
To implement a saga, and execute a server request, we call a fetch command and provide it:
If the request succeeds and we receive a response, we dispatch a new action called SAVE_RECIPE_SUCCESS with the received json payload. If the request fails for any reason we are trown to the catch clause. We dispatch a SAVE_RECIPE_FAILURE action containing the error message.
Both of these cases are handled by the reducer.
function* saveRecipe(action) {
try {
const res = yield call(fetch, action.url,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(action.payload)
});
const json = yield call([res, 'json']); //retrieve body of response
yield put({type: recipeConstants.SAVE_RECIPE_SUCCESS, payload: json});
} catch (e) {
yield put({type: recipeConstants.SAVE_RECIPE_FAILURE, message: e.message});
}
}
Once we are done with defining the saga, we define how the saga is handled. We can takeEvery or takeLatest. In our case we will be using takeEvery which tells Redux to handle every action dispatched of this type.
The code below defines the saga to handle every SAVE_RECIPE dispatched.
function* AddRecipePageSagas() {
yield takeEvery(recipeConstants.SAVE_RECIPE, saveRecipe);
}
export default AddRecipePageSagas;
Once the action is handled by the saga, two possible outcome actions may be dispatched:
We add those two cases to our reducer for proper handling.
const addRecipePageReducer = (state = initialState, action) => {
switch (action.type) {
case recipeConstants.SAVE_RECIPE_SUCCESS:
return state
.set('lastSaved', fromJS(action.payload.lastSaved))
.set('_id', fromJS(action.payload._id));
case recipeConstants.SAVE_RECIPE_FAILURE:
console.log(recipeConstants.SAVE_RECIPE_FAILURE + ' reducer');
return state;
}
}
This tutorial provided the basic details of the following elements:
Created: July 30th, 2018 Updated: July 30th, 2018