React Redux Saga example app.

Ron Lavit
5 min readJun 25, 2018

--

Click Here to see a live example of what we’ll be building.

Redux-saga is a redux middleware library, that is designed to make handling side effects in your redux app nice and simple. It achieves this by leveraging an ES6 feature called Generators, allowing us to write asynchronous code that looks synchronous, and is very easy to test.

So why should we use redux-saga? Contrary to redux thunk, you don’t end up in callback hell, you can test your asynchronous flows easily and your actions stay pure.

In this very basic App I have only one button which can make a call to the NEWS API . This call requests only one news from the CNN channel -the top one.

The App has generally three state :

STATE(before call) →STATE(during call) →STATE(answer received)

Let’s have a quick look at the file structure of my example.

src/
components/
App.js
containers/
Button.js
NewsItem.js
Loading.js
actions/
index.js
reducers/
index.js
sagas/
index.js
index.html
index.js
loading_spiner.gif

I presume you know how to set up a development environment and install all the necessary dependencies . Thus I am not going into details about that.

Now lets go step by step .

index.js

import React from 'react';
import createSagaMiddleware from 'redux-saga';
import { render } from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import { logger } from 'redux-logger';
import reducer from './reducers';
import App from './components/App';
import rootSaga from './sagas';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware, logger),
);sagaMiddleware.run(rootSaga);render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
if (module.hot) { module.hot.accept(App);}

In this file I’ve initialized redux store . This store uses two middlewares logger and SAGA. Redux-logger tool to inspect in console panel triggered actions and state of Redux store.

APP.js

import React from 'react';
import Button from '../containers/Button';
import NewsItem from '../containers/NewsItem'
import Loading from '../containers/Loading'
let App = () => (
<div>
<Button />
<Loading />
<NewsItem />
</div>
);
export default App;

This file is quite simple. It’s shows only three component.In the first state it’s displays only a button , during a call to API the spinner appears, and after the response have received <NewsItem /> is being shown.

Button.js

import React from 'react';
import { connect } from 'react-redux';
import { getNews } from '../actions';
let Button=({getNews})=>(
<button onClick={getNews}>Press to see news</button>
)
const mapDispatchToProps = {
getNews: getNews,
};
Button = connect(null,mapDispatchToProps)(Button);export default Button;

<Button /> component displays just button. I connect this component to Redux using connect function from react-redux. onClick method of this component triggers getNews action,which I am going to write next.

actions/index.js

export const getNews = () => ({
type: 'GET_NEWS',
});

Action creator getNews is very simple .It returns an object with only type of action.

And let a reducer know how to deal with this type of actions.By now reducer will looks like this :

reducers/index.js

const reducer = (state = {}, action) => {
switch (action.type) {
case 'GET_NEWS':
return { ...state, loading: true };
default:
return state;
}
};
export default reducer;

When action ‘GET_NEWS’ was dispatched property of state loading becomes equal to true and the spinner is appears on the screen.And of course call to API should be triggered and after response have arrived action ‘NEWS_RECEIVED’ must be dispatched. Redux-Saga will take care about all this.Next let’s go to the main file of this SAGA tutorial.

sagas/index.js

import { put, takeLatest, all } from 'redux-saga/effects';function* fetchNews() {  const json = yield fetch('https://newsapi.org/v1/articles? 
source= cnn&apiKey=c39a26d9c12f48dba2a5c00e35684ecc')
.then(response => response.json(), );
yield put({ type: "NEWS_RECEIVED", json: json.articles, });
}
function* actionWatcher() {
yield takeLatest('GET_NEWS', fetchNews)
}
export default function* rootSaga() {
yield all([
actionWatcher(),
]);
}

This file is obviously the most complicated one. I guess it looks quite challenging because of unusual syntax of ES6 GENERATORS like ‘yield’ and ‘*’. We export from this file function rootSaga in which we call function actionWatcher.

function* actionWatcher() {
yield takeLatest('GET_NEWS', fetchNews)
}

To put it simple it’s like I’m telling SAGA to wait for action ‘GET_NEWS’ to get dispatched. And ones ‘GET_NEWS’ was dispathced to call fetchNews function. Inside of fetchNews function happens asynchronous call to API and when request arrived next action { type: “NEWS_RECEIVED”, json: json.articles, } is dispatched. As you can see we don’t even need to write action “NEWS_RECEIVED” in actions/index.js file because it’s fully described here.

By now let reducer know how to deal with this type of actions “NEWS_RECEIVED”.

reducers/index.js

const reducer = (state = {}, action) => {
switch (action.type) {
case 'GET_NEWS':
return { ...state, loading: true };
case 'NEWS_RECEIVED':
return { ...state, news: action.json[0], loading: false }
default:
return state;
}
};
export default reducer;

So this is final code of reducer. Once response from the API call has received Redux state will have property news which contains json of one news.

NewsItem.js

import React from 'react';
import { connect } from 'react-redux'
const imgStyle = {
hight: 'auto',
width: '80%',
border: '4px solid RebeccaPurple ',
borderRadius: '5%'
};
const articleStyle = {
width: '50%',
margin: '0 auto',
color: 'olive'
}
let NewsItem = ({ article }) => (
article ?
<article style={articleStyle} >
<div>
<h1>{article.title}</h1>
<img style={imgStyle} src={article.urlToImage} alt="" />
<h4>{article.description}</h4>
<a href={article.url} target="_blank">READ MORE</a>
</div>
</article> :
null
);
const mapStateToProps = (state) => ({
article: state.news,
})
NewsItem = connect(mapStateToProps,null)(NewsItem)
export default NewsItem;

<NewsItem/> component displays the received news.

Loading.js

import React from 'react';
import { connect } from 'react-redux'
import img from '../loading_spinner.gif'
let Loading = ({ loading }) => (
loading ?
<div style={{ textAlign: 'center' }}>
<img src={img} alt='loading' />
<h1>LOADING</h1>
</div> :
null
);
const mapStateToProps = (state) => ({loading: state.loading})Loading = connect(mapStateToProps,null)(Loading)export default Loading;

<Loading/> component displays spinner during async call to API.

You can see the full App code into my repository :

Please ‘clap’ if you find this article useful.

--

--

Ron Lavit

Frontend | React developer. Currently based in Poland.