Build a simple app that fetches dog images using React, Redux & Redux-Saga
Michael Gilâââhttps://www.flickr.com/photos/msvg/
I began using [redux-saga](https://redux-saga.js.org/)
at work in a fairly complex boilerplate.
The path to becoming even a little bit comfortable with it was winding and suboptimal, so I thought Iâd write the tutorial I wish Iâd had when just starting out with sagas.
This tutorial assumes you have a solid grasp of React or React Native and Redux. If youâre not ready for
redux-saga
(yet), Iâd definitely recommend that you check out this amazing, MASSIVE treasure trove of resources for learning React and Redux.
markerikson/react-redux-links_react-redux-linksâââCurated tutorial and resource links Iâve collected on React, Redux, ES6, and more_github.com
What youâll build
Dog Saga - Redux-Saga Beginner Tutorial_Super simple app for a beginner redux-saga tutorial._ryanjyost.github.io
Whatâs redux-saga? Why would I want or need it?
From the official repo:
redux-saga
is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, simple to test, and better at handling failures.
I think of it as an organized way of helping your Redux app communicate and stay in-sync with the outside worldâââmainly external APIs.
Many wonderful people have opined on the pros, cons, and everything else about redux-saga
, much better than I couldâââso here are some links if you want to really nail down the WHATs and WHYs.
Weâll be focusing on the HOW.
Read Me ¡ Redux-Saga_The mental model is that a saga is like a separate thread in your application that's solely responsible for sideâŚ_redux-saga.js.org
Managing Side Effects In React + Redux Using Sagas_For me, there has always been one thorn in the React+Flux setup, which is that more complicated processes involvingâŚ_jaysoo.ca
Redux Saga: The Future is 1975_WTF is a clock? (This is the first post in a three-part introduction to Redux Saga. The second post is here, and theâŚ_ohyayanotherblog.ghost.io
Using redux-saga To Simplify Your Growing React Native Codebase_Some of the most fragile, embarrassing, and unreadable code you will write is flow control.âââBeowulf_shift.infinite.red
What is Redux-Saga?_Redux-saga is a redux middleware library, that is designed to make handling side effects in your redux app nice andâŚ_engineering.universe.com
Redux nowadays : From actions creators to sagas_There's this expression I say each time I stumble on something (some technology) that's going to change my habits inâŚ_riad.blog
Master Complex Redux Workflows with Sagas_Learn how to handle intricate procedures with async watcher routines (sagas) that monitor your Redux events. BrandonâŚ_konkle.us
Letâs get started
Create a new app with [create-react-app](https://github.com/facebookincubator/create-react-app)
.
npx create-react-app dog-saga
Confused by the
npx
? So was I, until I read this.
Enter the projectâs directory and fire up the app.
cd dog-saganpm start
You should now see the boilerplate for create-react-app
, with itâs spinning React logo. Youâll be replacing it with cute dogs soon enough.
Redux
Install redux.
npm install --save redux
Create a new file in your src
folder called redux.js
and add the following code to it.
We have three action types and a single reducer
**API_CALL_REQUEST**
says that weâre beginning the process offetching
a dog from the Dog API.**API_CALL_SUCCESS**
tells the Store that we successfully retrieved adog
and are therefore no longer in the process offetching
one.**API_CALL_FAILURE**
tells the Store that something went wrong with our API call. We received anerror
rather than a newdog
.
But how should we make theAPI_CALL_REQUEST
?
How does the Store know whether the API call was a success or a failure?
HOW DO WE GET PICTURES OF CUTE DOGS DAMNIT??? With a saga.
P.S. Weâre not gonna use action creators in this app. For something so simple, they may muddy the waters. Also, youâll see how redux-saga handles and dispatches actions more clearly (in my opinion) without them.
Redux-Saga
We want to create a saga, using redux-saga
, that will initiate an API call for a dog image, then tell the Store whether that API call was a success or a failure.
- If successful, weâll get a new
dog
and dispatchAPI_CALL_SUCCESS
along with thedog
. - If a failure, weâll get an
error
and dispatchAPI_CALL_FAILURE
along with theerror
.
Install redux-saga
.
npm install --save redux-saga
Also install [axios](https://github.com/axios/axios)
, which will help us make [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
-based API calls.
npm install axios
Create a new file called sagas.js
and add the following code to it.
Before we walk through this new file, notice the function*
syntax. This creates a special kind of function new to ES6 called a [generator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*)
.
Generators can pause and restartâââbe exited and re-enteredâââand actually remember the context/state of the function over time.
Each yield
in a generator basically represents an asynchronous step in a more synchronous/sequential processâââsomewhat like await
in an [async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)
function.
redux-saga
relies on generators, but does a decent amount of the work for us, so (in my fairly limited experience) a deep understanding of them for this use-case isnât necessary.
Here are some resources if you want to learn more about generators
The Basics Of ES6 Generators_ES6 Generators: Complete Series The Basics Of ES6 Generators Diving Deeper With ES6 Generators Going Async With ES6âŚ_davidwalsh.name
The Hidden Power of ES6 Generators: Observable Async Flow Control_In 7 Surprising Things I Learned Writing a Fibonacci Generator in JavaScript, I covered one obvious use-case for ES6âŚ_medium.com
Now letâs walk through **sagas.js**
- a
**watcherSaga**
is a saga that watches for an action to be dispatched to the Store, triggering aworkerSaga
. [takeLatest](https://github.com/redux-saga/redux-saga/tree/master/docs/api#takelatestpattern-saga-args)
is a helper function provided byredux-saga
that will trigger a newworkerSaga
when it sees anAPI_CALL_REQUEST
, while cancelling any previously triggeredworkerSaga
still in process.**fetchDog**
simply usesaxios
to request a random dog image from the Dog API and returns a[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
for the response.**workerSaga**
attempts tofetchDog
, using anotherredux-saga
helper function[call](https://github.com/redux-saga/redux-saga/tree/master/docs/api#callfn-args)
, and stores the result (a resolved or failedPromise
) in aresponse
variable.- If
fetchDog
was a success, we extract thedog
image from theresponse
and dispatch anAPI_CALL_SUCCESS
action withdog
in the payload to the Store, using ANOTHERredux-saga
helper function[put](https://github.com/redux-saga/redux-saga/tree/master/docs/api#putaction)
. - If there was an error with
fetchDog
, we let the Store know about it by dispatching anAPI_CALL_FAILURE
action with theerror
.
Phew! Itâs a little weird at the beginning, but this pattern/procedure and its benefits become more clear after a few implementations.
Hook up React, Redux and Redux-Saga
Ok, we have our pieces, now itâs time to put them all together.
Install [react-redux](https://github.com/reactjs/react-redux)
.
npm install --save react-redux
Also, install Redux Devtoolsâââmust-have for debugging and seeing Redux (and sagasâ related actions) in action. Go here for info on setting up the Redux dev tools. I recommend using the browser extension.
Open your index.js
file and make it look like the file below.
The Redux stuff should look familiar.
[createStore](https://github.com/reactjs/redux/blob/master/docs/api/createStore.md)
with ourreducer
- Connect the Redux DevTools to the Store for debugging and learning
- And wrap the
<App/>
component in a<Provider/>
component with thestore
, which letâs us work with Redux in React. - Weâll actually
_connect()_
the_<App/>_
component shortly.
To make our redux-saga
work with ReduxâŚ
createSagaMiddleware
, and apply it to the Reduxstore
with some help from[compose](https://github.com/reactjs/redux/blob/master/docs/api/compose.md)
and[applyMiddleware](https://redux.js.org/docs/api/applyMiddleware.html)
run
thewatcherSaga
, so that it can trigger theworkerSaga
when thereâs anAPI_CALL_REQUEST
Connect <App /> to Redux
Open up App.js
and paste the following code into it.
mapStateToProps
to make the most current state offetching
,dog
anderror
available asprops
in theApp
component.- Using
mapDispatchToProps
, we create a function calledonRequestDog
that dispatches anAPI_CALL_REQUEST
action to the Store. connect
theApp
component and export this âreduxedâ version of it for use inindex.js
.
Bring it all together on the screen
Now letâs walk through some of the changes (top to bottom) made to the App
componentâs rendered output, which allow the user to see the current state of the app and request dog images.
All of these snippets are from the **App.js**
above, so no new code here.
In the snippet below, we tweaked the image src
to show a dog
image if one exists in the Store. If dog
is null
, it falls back to the React logo
.
If the current state
of our app has a dog
image, we tell the user to keep clicking. If not, we tell them to replace the React logo
with a dog
.
If the current state
has an error, we display some text to let the user know.
Here, if our sagas are currently in the process of fetching
a new dog image, which means workerSaga
has not dispatched an API_CALL_SUCCESS
or API_CALL_FAILURE
yet, we disable the button.
Otherwise, we provide a button for the user to click and request a random dog image.
Just for fun, letâs cause an error
To see the workerSaga
dispatch an API_CALL_FAILURE
, go into sagas.js
and mess up the url
for the dog api (like changing âbreedsâ to âbedsâ).
Now when you click the âRequest a Dogâ button, the error message displays!
A recap, step-by-step
- An event takes placeâââe.g. user does something (clicks âRequest a Dogâ button) or an update occurs (like
componentDidMount
) - Based on the event, an
action
is dispatched, likely through a function declared inmapDispatchToProps
(e.g.onRequestDog
) - A
watcherSaga
sees theaction
and triggers aworkerSaga
. Use saga helpers to watch for actions differently. - While the saga is starting, the
action
also hits areducer
and updates some piece ofstate
to indicate that the saga has begun and is in process (e.g.fetching
). - The
workerSaga
performs some side-effect operation (e.g.fetchDog
). - Based on the result of the
workerSaga
âs operation, it dispatches anaction
to indicate that result. If successful (API_CALL_SUCCESS
), you might include a payload in the action (e.g.dog
). If an error (API_CALL_FAILURE
), you might send along anerror
object for more details on what went wrong. - The
reducer
handles the success or failureaction
from theworkerSaga
and updates the Store accordingly with any new data, as well as sets the âin processâ indicator (e.g.fetching
) to false.
Throughout this process, you can use the updates to Redux state
flowing through to props
to keep your user informed of the process and progress thereof.
More resources
Async operations using redux-saga_UPDATE August/2017:_medium.freecodecamp.org
This collection of common Redux-saga patterns will make your life easier._This is a 2-part seriesâââfeel free to check out the first part here._medium.freecodecamp.org