What is Redux-Saga?
Redux-saga is a redux middleware library, that is designed to make handling side effects in redux.
As I said before they were introduced to me by Mateusz Bosek, and he liked them. Moreover, since I began using sagas, I’ve also grown to love them and the redux-saga pattern.
The Redux-Saga Pattern
Redux-Saga leverages Generators, allowing us to write asynchronous code that looks synchronous, and it is easily testable – This right here is what makes redux-saga powerful, and this is why I love the redux-saga pattern.
For example, let says you have a user login process which does the following:
Get the username and password from state
Call login API endpoint with the username and password – on success get the user authorisation token
Save auth token in the store
Remove the username and password from the store
Call Settings API endpoint with the authorisation token – on success get the user settings
Set the user settings in store
This is what the saga looks like for the user login process:
In this example above I’ve used what redux-saga calls Effects, these Effects are:
select: Used to select state from redux store (same as getState())
call: Used to call other methods with arguments
put: Dispatches action
So my userLoginSaga is okay. However, It needs some refactoring.
So my userLoginSagaRefactor is much better. The API calls now have their own sagas. Additionally, this version is much better for unit testing. In the unit test I can test each yield, like the following:
This unit test will check each yield. The order is essential, otherwise, the test will fail. There are other forms of testing redux-saga for example redux-saga-test-plan
Conclusion
I am forever grateful to Mateusz Bosek for introducing me to redux-saga. It’s made the applications I work on better and improved my coding experience.
My mission with this blog post is to show you my current development tool-belt for creating a new web application:
What core technology do I use and why?
What packages do I use and why?
How do I structure my code and why?
Starting with the Core Technology
My coding core technology is JavaScript. Why JavaScript?
From 2005 to 2012 I use to build web applications (monoliths) using ASP.NET. In 2013 I made the switch to start separating my Back-end from my Front-end by using AngularJs for my Front-end. This switch gave me the ability to unit test my Front-end. The Back-end became a simple REST API that could be consumed by other applications as well, example Mobile Apps. It was also far more comfortable to code Web Apps in AngularJS using JavaScript, and I was hooked on JavaScript for everything. It was around this time I also started experimenting with nodejs (However, that’s for another blog post in the future. Back to my current development tool-belt)
So fast-forward to now; JavaScript keeps getting better and better, the community keeps creating amazing open source technologies for us to use. The ECMAScript Wizards keep adding new features and keep making JavaScript great.
My web-app core technology is React. Why React?
I’ve invested in books, front-end masters courses and many many more hours spent on blogs and tutorials. I’ve also been using it at work since 2017 till now. I know the ins and outs of React quite well, and with React you’ve got React Native as well which allows you to do mobile apps. At the moment React is my default front-end tool, and until something comes along that’s going to dethrone React as React did with Angularjs then I might consider it, but for now, its all React for me.
Okay, great so at this point, I am using JavaScript and React.
When I start a new project, I use create-react-app. create-react-app is a React boilerplate for more info go to https://github.com/facebook/create-react-app
Why I use create-react-app?
I use it for everything. When I need to create a prototype to verify a package or feature quickly. By default it has all the basics you need:
Now that I have my core and project created, What packages do I use and why?
I am separating these into production dependencies (packages needed by the web-app to run in production) and development dependencies (excellent packages which make my development workflow superb)
React-Intl for Internationalising – https://github.com/yahoo/react-intl Why React-Intl?
Before moving to Dubai, UAE. This package would not be on the list because all applications I worked on in South Africa only supported the English language, therefore, there was no need for React-Intl. However, this was just a lazy design because what if we did want to support an additional language we would have to refactor the entire application. So right now any application I work with has React-Intl by default even if only one language is used, at the start. If I need to add additional language support, the application is ready to support that.
$ yarn add react-intl
Material-ui for UI (Layout, Theme and Styling Components) – https://github.com/mui-org/material-ui Why Material-ui?
There’s a massive community behind this open source project. React components that implement Google’s Material Design.
$ yarn add @material-ui/core
Development Dependencies
Because I used create-react-app to set up my React development. I already have the following:
Jest – for unit testing. I update my package.json file by setting the unit test code coverage.
Typescript – for type checking (In 2019 you must add types to your code.)
Prettier – for code format. https://github.com/prettier/prettier Why Prettier?
No one has time to format their code :D, therefore, let prettier format your code for you. Decide your format rules upfront and let prettier do all the work. I configure prettier with husky and lint-staged to automatically format every file I commit. Therefore all code committed to GitHub is formatted automatically by prettier.
Okay, so now that I covered my core, and my main production and development packages. Next on the list is the coding structure.
With this fantastic React development environment set up with these main packages, and unit test coverage setup. All is useless if the coding structure is not modular or lacks a good structure that can scale.
Folder structure
config folder
This is where I set up my root reducers, sagas, etc.
modules folder
The modules folder is where I create my features for my web-app. Each feature has a components, containers and pages folder. Regarding nesting, before I’ve had a situation where I went crazy with nesting components inside components, this resulted in a complex nesting and over time the structure became difficult to work use. So the rule I use now, is I don’t go past 1 level of nesting. This means in the parent components is on the same level as children components. At first, this seems weird over time, and I got used to it. The same rule applies to containers and pages.
shared folder
In the shared folder, I place all items which are common or used in multiple places. This also where I place the API interface as well. The same nesting rules apply for shared folder structure.
Summary
Currently, this is I what use and its working for me. However, this is not cast in stone; it’s like an evolving style guide. Additionally, the packages and patterns that I am using are working for me right now, once again when I come across a package or patterns which works better or easier, I evaluate it, try it out and then it gets added to my tool-belt. Repo:https://github.com/zulucoda/zulucoda-tool-belt-app
So with this post, I am trying something different. I am just going to tell you about my experience thus far with taking an existing SPA created in Backbone + jQuery and migrating this SPA to React.
I am going to get straight into it, and therefore if you’re new to React, my suggestion would be to check out other blog posts as this will cover advanced topics.
Additionally, I know that many people have written articles & posted videos on such migration. However, when I was reading and watching those videos, I was still missing critical architectural info. So even though sample code and blog post example were made available to me, this still didn’t help me in what I needed to do.
Migration Planning Phase
After doing research, I presented a few ideas to my colleagues and Head of Development. There where two main ways of going about our migration:
Hybrid Solution
Complete Clean Refactor
Hybrid Solution
This would allow us to slowly migrate sections of our existing Legacy SPA over to React one feature at a time. The ability for React to run side by side with other JavaScript libraries/frameworks is a significant feature. This is something I completely missed, even though Facebook have always stressed this point about React’s abilities (more on this later).
Complete Clean Refactor
We start from the ground up and re-build everything. There’s a point in the life of a product where a complete clean refactor is not feasible. When you have a product that is successful and clients are happy. Yes, the underlying tech might be outdated, it might take longer to deliver new features and maybe its no-longer developer friendly.
Additionally, asking business just for a budget to refactor an existing product? It’s no wonder why business does not trust its tech people.
After presenting this information to the team, the Hybrid Solution was a clear winner.
Before joining the team, I had been working on a React SPA that used a NodeJs + Express API. I was working on both of these code bases. Additionally, we had a React Native application using the same API. Moreover, before that, I was working on an Angularjs (1.5) App. Therefore I had no Backbone experience. However, this would not be an issue.
So we had an idea that we should do a hybrid solution, but we didn’t know how we would do it? Therefore it was suggested that we start by prototyping this hybrid solution.
The Prototype Phase
This phase would last two weeks, and we would use the learning’s from this phase to create an architectural solution, which we will use going forward for our smooth react migration.
During this prototype phase, I tried many solutions to get React working side by side with the Legacy SPA. Going back to the statement above about React being able to run side by side with other JavaScript libraries, I completely missed this in the first week of prototyping. I had created a complex solution evolving an iframe which would run the React app in the iframe. Then I had built this custom middleware which would hide and show the iframe when navigating too and from the Legacy SPA App to the React App. At first, this was a neat solution. It looked great and worked well.
When I presented my solution to my colleagues, they were happy with the outcome. However, they challenged me to simplify the architecture and use Redux Architecture with Redux-Saga as middleware and making use of Redux-Saga side effects for rendering components onto the DOM. This would get rid of the iframe and simplify the middleware to dispatch actions to redux and sagas and make use of Backbone events. This way any new developer joining the project would not need to learn my custom middleware but use existing tech knowledge they know about Redux and Backbone.
The Final Architecture
I cannot post the actual code as that is company property. However, the beauty of these patterns I used during the prototype phase are so simple and straightforward that I have recreated them for this blog post.
React App
Redux
React-Redux
Redux-Sagas
Legacy SPA App
jQuery
Some JavaScript Library
React-Middleware – used for dispatching actions
So the Legacy SPA does not have a clue about React it just continues to run as it does currently. We have a React-middleware which dispatches actions from the Legacy SPA to Redux or Sagas.
Storing User Info in Redux
So example currently on our SPA when a User logs in, after login, the react-middleware dispatches an action with User login payload, this is sent to Redux and the User data is stored in our Redux store for use for by our React App.
Rendering/Mount React Component
This is probably one of the coolest things I learnt about React which is the possibility of rendering multiple components onto the DOM. Now I didn’t know React could do this. However, I recently learnt from a conference talk that this is how they use React at Facebook.
In our SPA we decided to use redux-sagas side-effects to also render components onto the DOM for us. Therefore, on the Legacy SPA, an action is dispatched with a DOM element ID. Then a saga listening to this action uses that element ID to render a component onto that DOM element using ReactDOM.render(). The great thing about this is we are respecting the Redux Architecture, by simple dispatching an action, this easy to understand even for new people joining our team.
Un-Rendering/Dismount React Component
Where are we now
We have just finished our first feature in React, and it is working great inside the Legacy SPA. We are about to start creating more new features using React rapidly. We will also start looking at converting existing functionality over to React. The remarkable thing about this Hybrid solution, is we are keeping business happy by delivering the features they want. We as developers have created a great developer experience for ourselves.
So you have been using React now for some time, and you want to know what you have to test, and If you have seen one of Brian Holt’s videos on FrontEndMaster on Introduction React and Redux. In this video, Brian says that at Netflix they do not have unit tests for their React components because they change so often. The problem with this statement is a new guy, or an inexperienced team might take this out context to say that if Netflix does not write unit tests for their React components why should we. My problem with this is these people can use this as an excuse. Also, we have to look at the capabilities here, Brian Holt is a super talented developer, and at Netflix, He is surrounded by the best of the best, so for them, it works because they are operating on another level (rockstar level lol ??).
I come from Angular 1.5 where it was straightforward what needed to be unit tested. We wrote unit tests for controllers, components, services, directives and custom modules. The view was tested with e2e tests using protractor plus. With these you could get very impressive test coverage, one project I worked on we had 100% test code coverage. However, that was Angular 1.5 days, and we have since moved on from that to React. Like I had with Angular 1.5, I want all my React applications to have excellent test coverage.
With all the React applications I have built thus far I have focused a lot on testing, and within each app, I have been tinkering with different approaches. Moreover, I think I have found a good approach which works well for most applications including enterprise.
Redux: Test all your reducers and actions. In fact, my suggestion would be to use TDD (Test Driven Development) for this. Testing your reducers and actions has nothing to do with React, at this stage. If you are coming from Angular 1.5, you can think of this as testing some custom modules which had nothing to with Angular.
Note: If you are building a React application my suggestion is always to use Redux for State management. React + Redux are made for each other.
Testing an action & reducer Note: This test below is making sure that when the setTunes() action is dispatched to the reducer, the expected state should have the tunes array set with the tunes received from setTunes().
//file-name: react-tunes-player-reducer.spec.js
describe("set tunes", () => {
it("should return state with tunes set when setTunes action is dispatched", function() {
const action = setTunes(tunes());
const actual = ReactTunesPlayerReducer(stateBefore(), action);
const expected = {
...stateBefore(),
tunes: [...tunes()]
};
expect(actual).toEqual(expected);
});
});
Step 2: Test the Container (RCV)
Test your container. You should still use TDD (Test Driven Development) for this. Here you are testing your connected component, basically testing the glue between Redux and Your React Component. The key functions are mapStateToProps and mapDispatchToProps.
Testing mapStateToProps Note: These tests below make sure that we have mapped state with the props we want to pass down into our view.
Step 3: Testing the View (RCV)
Test your view. This is the controversial part which guys like Brian Holt and many others have said not to waste your time testing this is because the view always changes. My suggestion is the following:
Newbie – New to React: Test everything in your view
Inexperienced Team – New to React: Test everything in your view
Everyone else: Depending what your component does:
Test all component methods like componentDidMount, componentWillReceiveProps and etc..
If your component renders differently according to state, test those different render states
Testing different render states, plus componentDidMount and componentWillReceiveProps
//file-name: react-tunes-player-view.spec.js
describe("when not rendered with tunes", () => {
beforeEach(function() {
wrapper = shallow();
});
it("should render React Tune Player View with warning message displayed", function() {
expect(wrapper.contains("Warning! No tunes loaded in player.")).toBe(
true
);
});
describe("componentDidMount", () => {
it("should NOT add event listener when tunes props is supplied", function() {
spyOn(
ReactTunesPlayerView.prototype,
"componentDidMount"
).and.callThrough();
wrapper = mount();
expect(
ReactTunesPlayerView.prototype.componentDidMount
).toHaveBeenCalledTimes(1);
expect(wrapper.find(".warning-wrapper").length).toEqual(1);
});
});
describe("componentWillReceiveProps", () => {
it("should NOT call playCurrentTune when isPlaying is true", function() {
wrapper = mount(
);
wrapper
.instance()
.componentWillReceiveProps({ _player: { isPlaying: true } });
expect(playCurrentTuneMockFunc).toHaveBeenCalledTimes(0);
});
it("should NOT call pauseCurrentTuneMockFunc when isPlaying is false", function() {
wrapper = mount(
);
wrapper
.instance()
.componentWillReceiveProps({ _player: { isPlaying: false } });
expect(pauseCurrentTuneMockFunc).toHaveBeenCalledTimes(0);
});
});
});
So if you are a Newbie or Inexperienced Team just test everything you will learn a lot about react, redux, enzyme, jest, shallow rendering and jsdom. However if you are NOT going to write tests for your view, the minimum you may check is if the React component still renders after making changes, and this can be done with enzyme using shallow rendering.
Minimum view tests – check if component renders without crashing
import React from 'react';
import { shallow } from 'enzyme';
import App from './App';
it('renders without crashing', () => {
shallow(<App />);
});
Just like you, I am also always learning and getting better, so the above is my take at this moment in time. I will continue to tweak, experiment and improve. If there’s anything that you come across or a better method, please will you kindly share it? I am open to improvements.