The React Blog Series: Part Five This article is part five in a weekly series on building a blog with React and is an extension of the code created in previous parts. The React Blog Series Replacing Redux Thunks with Redux Sagas ( ) Writing Unit Test for a React Blog with Redux Sagas Building a website with React and Bulma Part One: Building a Blog with React and Contentful Part Two: Import your Medium Feed into React Part Three: Adding a Redux to a React Blog Part Four: Part Five: Part Six: In Progress First of all, let me start with on this subject. I have only actually been using Sagas for the last two weeks. If you’re anything like me, as you read this, you will constantly be asking yourself, why the hell would anyone want to use this, it’s massive overkill, and you would be absolutely correct. It is overkill… for what I’m doing, but at the end of the day Sagas are better, more scalable, and infinitely easier to unit test which we will see in Part Six. If you don’t like my explanations or examples, I understand. There is also SO MUCH more to Sagas than what I’m going to use here. Good Luck! — I am by no means an expert Here is the link to the Redux Saga documentation. Aaron Klaser What is Redux Saga? Sagas are a redux middleware that handles triggered in your application often referred to as ‘ . They are constantly watching for certain actions to be dispatched, before they but in and take over controller of the situation. asynchronous actions side effects’ Sagas are implemented as that objects to the redux-saga middleware. Generator functions yield Wait, what is a Generator function? !@#$ing magic, thats what! But, heres a better explanation which I stole from . Mozilla Generators are functions which can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances. Calling a generator function does not execute its body immediately; an object for the function is returned instead. When the iterator’s method is called, the generator function's body is executed until the first expression, which specifies the value to be returned from the iterator or, with , delegates to another generator function. The method returns an object with a property containing the yielded value and a property which indicates whether the generator has yielded its last value as a boolean. Calling the method with an argument will resume the generator function execution, replacing the expression where execution was paused with the argument from . iterator next() [yield](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield "The yield keyword is used to pause and resume a generator function (function* or legacy generator function).") [yield*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield* "The yield* expression is used to delegate to another generator or iterable object.") next() value done next() yield next() A statement in a generator, when executed, will make the generator . If a value is ed, it will be passed back as the . A generator which has returned will not yield any more values. return done return value Okay, Back to Sagas Now where was I? Ah yes… Sagas are implemented as Generator functions that objects to the redux-saga middleware. The yielded objects are a kind of instruction to be interpreted by the middleware. When a Promise is yielded to the middleware, the middleware will suspend the Saga until the Promise completes. Once the Promise is resolved, the middleware will resume the Saga, executing code until the next yield. yield Redux Saga middleware does a lot of the Generator magic for us behind the scenes yielding to saga functions like put, all, call, and takeLatest. What happens is that the middleware examines the type of each yielded Effect then decides how to fulfill that Effect. If the Effect type is a then it will dispatch an action to the Store. If the Effect is a then it'll call the given function. This separation between Effect creation and Effect execution makes it possible to test our Generator in a surprisingly easy way using PUT CALL next() In short, are pause able functions like a music track, and are the DJ that press the pause play and stop buttons on the track. Generators Sagas But enough chit chat, let’s get started! Install Saga npm install redux-saga Then in let’s connect Saga. Saga is a redux middleware so we will be injected it into our redux applyMuiddleware function, but we will have to create it first. store/index.js import { createStore, applyMiddleware } from 'redux'import { rootReducer } from './rootReducer'import reduxImmutableStateInvariant from 'redux-immutable-state-invariant'import thunk from 'redux-thunk'import { composeWithDevTools } from 'redux-devtools-extension'; import createSagaMiddleware from 'redux-saga' const sagaMiddleware = createSagaMiddleware() export function configureStore() {return createStore(rootReducer,composeWithDevTools(applyMiddleware( thunk,reduxImmutableStateInvariant())))} sagaMiddleware, As of right now, this does nothing. We will need to initialize the sagas which need some set up before we can do that. We are going to basically gut our current thunk setups. Creating Our Saga Helpers We are going to use three helpers: helper, which will generate a type object with pending, success, and error. createAsyncTypes helper, which will generate our actions for us. createActions helper which we will use to replace our switch statements. createReducer In the folder create a file called store Utilities.js _///////////////////// ActionHelpers /////////////////////_const asyncTypes = {PENDING: 'PENDING',SUCCESS: 'SUCCESS',ERROR: 'ERROR'} export const = typeString =>Object.values(asyncTypes).reduce((acc, curr) => {acc[curr] = `${typeString}_${curr}`return acc}, {}) createAsyncTypes export const =(type, payload = {}) =>({ type, ...payload }) createAction _///////////////////// createReducer /////////////////////_export const =(initialState, handlers) =>(state = initialState, action) =>handlers.hasOwnProperty(action.type)? handlers[action.type](state, action): state createReducer I might revisit these in the future, I’m not 100% sold on the approach yet, but for now it works and it’s pretty clean createReducers Moving from Thunks to Sagas Update Blog Types Open store/blog/types.js Before, we only had 2 types, we used BLOG_LOADING to set the loading state for both complete and error, and LOAD_BLOG_SUCCESS to load our data on success. --- BEFORE --- export const BLOG_LOADING = 'BLOG_LOADING'export const LOAD_BLOG_SUCCESS = 'LOAD_BLOG_SUCCESS' /** * Blog Types */ But now, we are going to call our new help to createAsyncTypes which will create our three types automatically; Pending (loading ), Success (load data), Error (loading complete). --- After --- _*/_ { createAsyncTypes } from './../Utilities' /** * Blog Types import GET_BLOG_ASYNC createAsyncTypes('GET_BLOG') //Using ASYNC as a convention to know that I'll have three types. export const = This will make more sence in a moment. Update Blog Reducer Open store\blog\reducer.js This can be done one of two ways, we could just simply update the types in the switch cases to use the new type names we just created like this. switch (action.type) {case types. :return {...state,loading: true}case types. :return {...state,posts: action.posts,loading: false}case types. :return {...state,loading: false}default:return state} GET_BLOG_ASYNC.PENDING GET_BLOG_ASYNC.SUCCESS GET_BLOG_ASYNC.ERROR This is an example of what our types helper has done. It generated a type object with 3 values and using the convention _ASYNC we know that will contains PENDING, SUCCESS, and ERROR. createAsyncTypes GET_BLOG_ASYNC Let’s make this a little bit more intelligent by using out createReducer helper and passing it an object instead of a switch statement. Delete everything and paste in this _/*** Blog Reducer*/_import initialState from './../initialState'import { createReducer } from './../Utilities'import * as types from './types' export default createReducer(initialState.blog, {[types.GET_BLOG_ASYNC.PENDING](state) {return {...state,loading: true}},[types.GET_BLOG_ASYNC.SUCCESS](state, action) {return {...state,posts: action.posts,loading: false}},[types.GET_BLOG_ASYNC.ERROR](state) {return {...state,loading: false}}}) Yes, I know what your thinking and I agree. This does look more confusing then a switch statement and just as clean. It actually took me a minute to understand what that object was actually doing because it wasn’t something I had ever written out like this we before. It’s creating an object that each equals and an anonymous functions but it’s setting the keys using square brackets. obj = {key: fn,[key]fn} Or you can use arrow functions which you will need to a colon arrow and parentheses around the object. export default createReducer(initialState.blog, {[types.GET_BLOG_ASYNC.PENDING] (state) {...state,loading: true} ,[types.GET_BLOG_ASYNC.SUCCESS]:(state, action) => ({...state,posts: action.posts,loading: false}),[types.GET_BLOG_ASYNC.ERROR]:(state) => ({...state,loading: false})}) : => ( ) Which ever way you choice is fine, let I said I’m not 100% on board , but I think this can help create an async reducer creator in the future ;) yet Create Our First Saga In our folder create a file called store\blog sagas.js _/*** Blog Sagas*/_import * as contentful from 'contentful'import { all, call, put, takeLatest } from 'redux-saga/effects'import { actions } from './../Blog'import * as types from './types' const client = contentful.createClient({space: 'qu10m4oq2u62',accessToken: 'f4a9f68de290d53552b107eb503f3a073bc4c632f5bdd50efacc61498a0c592a'}) const fetchPosts = () => client.getEntries() function* getBlogPosts() {try {const posts = yield call(fetchPosts)yield put(actions.success(posts.items))} catch (e) {console.log(e)yield put(actions.error(e))}} export default function* () {yield all([takeLatest(types.GET_BLOG_ASYNC.PENDING, getBlogposts)])} So, what is this doing? Well, let’s start with the obvious. is my connection to my Contentful client calls our to Contentful to retrieve my posts and returns them as a Promise fetchPosts is our actual saga. It first yields the call to fetchPosts, saga magic kicks in and pauses the function and waits for Contentful to return the data. Then, once the data has been successfully returned, instead of returning a promise it actually return the data to and calls behind the scenes which tells the function to play again. It then calls which magically dispatches the the actions with the post items. If where to fail, an exception is thrown which triggers the catch. getBlogPosts const posts next() yield put fetchPosts() is the action watcher that is connected to the Redux Saga middleware. This is similar to trigger the actions, except instead of the call an action and the action being dispatched to the reducer, the saga watches for that actions to be called, then it intercepts and calls the saga. takeLatest tells Redux Saga to stop any previous running saga task if still running then run a new one. export default function* says when the action is called, stop any getBlogPosts tasks previously call that my be not be completed and call a new function. takeLatest(types.GET_BLOG_ASYNC.PENDING, getBlogPosts) types.GET_BLOG_ASYNC.PENDING getBlogPosts() The function is a kind of housing to hold all of our watchers for the blog sagas. If we have more actions, we could create more sagas and create more watchers to call those sagas, those additional watchers would go in side the all function. yield all We then export the default function, which creates gets injected into bound to the sagaMiddleware.run function in the rootSaga. Setting Up the rootSaga We need to add all our sagas to the sagaMiddleware so we can start all of our watchers when the app loads. In the folder, create a file called store rootSaga.js import blog from './blog/sagas' const sagas = [blog] export const initSagas = (sagaMiddleware) =>sagas.forEach(sagaMiddleware.run.bind(sagaMiddleware)) Right now, we only have the one saga. If we set up the Medium store with Saga’s then we would import medium and pass it in to the sagas array. we don’t need to import every function from our file, only t action watchers, which we wrapped in an all function which like like a reducer for your sagas. Note: blog/saga.js Actions, The New Face of Blog Now, that we have some sagas that can watch our actions we need to update our now out of data actions. If you have been reading along, you know that I follow the which in the case of the means the is my public app facing code, and everything in the folder is private to the store. Sagas aren’t called by the app, the actions are. currently contains our thunks but those are not necessary anymore, so we can delete them and replace them with the actions that the app will call that will trigger our sagas. We can then remove the file from our folder. fractal file structure store store/Blog.js store/blog store/Blog.js actions.js store/blog Here is the before and after of the file Blog.js --- Before --- import * as contentful from 'contentful'import * as actions from './blog/actions' const client = contentful.createClient({space: 'qu10m4oq2u62',accessToken: 'f4a9f68de290d53552b107eb503f3a073bc4c632f5bdd50efacc61498a0c592a'}) export function loadBlog() {return dispatch => {dispatch(actions.blogLoading())return client.getEntries().then(({items}) => {dispatch(actions.loadBlogSuccess(items))}).catch(error => {console.log(error)dispatch(actions.blogLoading(false))})}} We get rid of all that logic because that the Saga’s job now, and replace it with a simple actions object that out app can call. --- After --- import { createAction } from './Utilities'import * as types from './blog/types' export const actions = {pending: () => createAction(types.GET_BLOG_ASYNC.PENDING),success: (posts) => createAction(types.GET_BLOG_ASYNC.SUCCESS, { posts }),error: (error) => createAction(types.GET_BLOG_ASYNC.ERROR, { error })} The function doesn’t really do anything more then clean our code up by make it so that no matter how many things are actually passed in as the payload, this function always takes 2 arguments. I think in the future we can use this to help automate some of our async CRUD processes. createAction One Last Thing, Initialize The Sagas In our store/index.js ...import createSagaMiddleware from 'redux-saga' import { initSagas } from './rootSaga' const sagaMiddleware = createSagaMiddleware() export function configureStore() { createStore(rootReducer,composeWithDevTools(applyMiddleware(sagaMiddleware,thunk,reduxImmutableStateInvariant())))**initSagas(sagaMiddleware) const store = return store**} Originally, we were return createStore, but initSagas need sagaMiddleware to be initialized first. So, we need to create the store then pass sagaMiddleware to initSages which will start all our watchers. Instead doing something complex in the application root index to call initSagas we can just set a store variable to createStore, then call initSagas before return the store. That’s it! Granted, I only just barely scratched the surface of what Sagas can do, this is a good primer to getting them set up and actually working. Let’s Review We Installed Sagas We created some helpers for types and reducers. Updated our types Updated our reducer and learned some new technics Created our first saga Set up a rootSage Moved our actions to the public class Initialized our Saga on app loaded Yes this is way more complicated, and a fair of extra set up, and has kind of a step learning curve, and…. well you get the idea. But, it’s ultimately a better solution. It’s cleaner(ish), more scalable, more controllable, it’s real-time ready, and it’s infinitely easier to test which we will see in my article 😉 next() — Writing Unit Test for a React Blog with Redux Sagas Next