Getting the best of RxJS, Redux, and React
By: Gabriel Dos Santos
Tags: JavaScript, Hooks, Rxjs, React, Redux

The idea here is to create a React app using Redux but leveraging the RxJS library, and for this purpose, nothing is better than Redux+RxJS, which gives us the redux-observable.

The entire app will be made with function component and Hooks, if you are not familiar with it, check this out writing a simple react using hooks! Another thing very important is the RxJS lib, we will talk about it later on.

Without further ado, let’s create the first component.

import React from 'react'; import './BookStore.css'; const genres = ['Adventure', 'Romance']; const BookStore = () => {

return (

<div className="container">

<div className="title">

<img className="title-book-svg" alt="books"

src="images/book.svg"/>

<h3>Search, Find and Buy your book here!</h3>

</div>

<div className="search">

<input id="books" size="20"

className="search-input"

placeholder="Books, Author, Title."/>

<button className="search-button">

Search

</button>

</div>

<p> Or select your preferred genre</p>

<select id="genre" className="genre-select">

<option defaultValue>Select a genre</option>

{

genres.map(genre => {

return (<option key={genre}>{genre}</option>);

})

</select>

</div>

);

} export default BookStore;

A simple component with HTML/CSS and a hard code list of the genre, nothing in special yet, but it’s a start.

Just a first look!

The next thing is to get the genre’s list from a Restfull API, it would end up with a genre that’s not available or even without available books, besides making unnecessary API calls to retrieve books from a genre that doesn’t exist.

Let’s create a singleton DataService.js file that’ll handle the HTTP calls. Very simple, the idea is to reuse it as a data access layer from EPICS.

import Axios from 'axios-observable';

import { timeout, retry } from 'rxjs/operators'; const axiosObs = Axios.create({

headers: {'Content-Type': 'application/json;charset=utf-8'}

}); const DataService = {

request: (endPoint, body) => {

const rOptions = {url: endPoint};

body ? rOptions.data = body : '';



return axiosObs.request(rOptions).pipe(

timeout('3000'),

retry('2'),

);

}

}; Object.freeze(DataService); export default DataService;

There are a few things here, redux-observable needs to be installed and as it has two important dependencies rxjs and redux , both have to be installed as well.

npm install redux-observable rxjs redux

You may be wondering “How about Axios?” as you can see, “axios-observable” is a wrapper, it also provides an Axios instance with, and it does have dependencies as well such as, but rxjs, is already installed so, just run this.

npm install axios axios-observable

Also do not forget to install the Official React bindings for Redux, also we will leverage a few hooks that come from it.

npm install react-redux

Disclaimer: feel free to use any AJAX library you wish.

Now, let’s create the genre.js, redux file, everything will be in one file, as a big picture, for now.

import { ofType } from 'redux-observable';

import { map, mergeMap, catchError } from 'rxjs/operators'; import DataService from '../utils/DataService';

// initial state of Genre.

const initialState = {

genres: [],

loading: false,

fetched: false,

error: null,

};

// action types

const types = {

FETCH_GENRE_REQUEST: 'FETCH_GENRE_REQUEST',

FETCH_GENRE_SUCCESS: 'FETCH_GENRE_SUCCESS',

FETCH_GENRE_ERROR: 'FETCH_GENRE_ERROR'

};

// action to fetch a list of genre.

export const fetchGenre = () => ({type: types.FETCH_GENRE_REQUEST}); // reducers

export const genre = (state = initialState, action) => {

switch (action.type) {

case types.FETCH_GENRE_REQUEST:

return {

...state,

loading: true

};

case types.FETCH_GENRE_SUCCESS:

return {

...state,

fetched: true,

genres: action.payload,

};

case types.FETCH_GENRE_ERROR:

return {

...state,

error: action.payload

};

default:

return state;

}

}; // Epic

export const fetchGenreEpic = action$ => action$.pipe(

ofType(types.FETCH_GENRE_REQUEST),

mergeMap(() => {

return DataService.request('api/genre.json').pipe(

map(({data}) => {

return {

type: types.FETCH_GENRE_SUCCESS, payload: data.genres

};

}),

catchError(error => {

return {type: types.FETCH_GENRE_ERROR, payload: error}

}),

);

}),

);

Ok, that’s a very big step, indeed, let’s break it down.

As you may know, what we have here so far, is the Redux flow such as actions, reducers, but how about the Epic? here is where we start taking the Redux to another level, for those are familiar with RxJS, you know how handy and powerful is working with reactive programming, streams, complex async/side effects, operators and so on.

So, what is EPIC? from the doc: “It is a function which takes a stream of actions and returns a stream of actions. Actions in, actions out.”

Confuse? Imagine it as a listener, every time some action is dispatched, the epic will listen to it, nothing changes from Redux regular flow, except for Epic.

Redux + Observable = Cool 😎

Next step, Setting up the config store middleware.

import { createStore, applyMiddleware } from 'redux';

import { createEpicMiddleware } from 'redux-observable'; // reducer and Epic

import { genre, fetchGenreEpic } from './Genre'; const epicMiddleware = createEpicMiddleware(); const ConfigureStore = () => {

const store = createStore(

genre,

applyMiddleware(epicMiddleware)

);

epicMiddleware.run(fetchGenreEpic);

return store;

}; export default ConfigureStore;

In the index.js file, import Provider from react-redux, also the configStore that was just created and set it into the Provider tag.

import React, { StrictMode } from 'react';

import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import ConfigureStore from './redux/configStore'; import BookStore from './BookStore'; const store = ConfigureStore(); ReactDOM.render(

<StrictMode>

<Provider store={store}>

<BookStore/>

</Provider>

</StrictMode>

, document.getElementById('root')

);

That’s it, pretty much we got the entire app setup with redux-observable, but before we demo it, there are a few things left.

How to dispatch an action? How to get data from state+Redux? How to handle HTTP calls from a component?

Now, it’s time to create our first custom hooks which will handle the dispatch action.

Create a file called useActions.js

import { useDispatch } from 'react-redux';

import { fetchGenre } from '../redux/Genre.js' const useActions = () => {

const dispatch = useDispatch();

const fetchGenreAction = () => dispatch(fetchGenre());



return {

fetchGenreAction

}

}; export default useActions;

So basically what’s going here is, a custom hook is created to provide actions to a component, in this way, all actions are in one place, and it can be used just by importing the useActions hook, leaving the component much cleaner.

Here is our BookStore.js component after all imports, useActions, useSelector, useEffect.

import React, { useEffect } from 'react';

import { useSelector } from 'react-redux'; import useActions from './hooks/useActions'; import './BookStore.css'; const BookStore = () => {

// Get action from custom hook.

const {fetchGenreAction} = useActions();



// Get Redux state using a hook

const {

genres, loading, fetched, error

} = useSelector(state => state);



// This useEffect will trigger only once, when the component is mounted.

useEffect(() => {

fetchGenreAction();

}, []);



return (

<div className="container">

<div className="title">

<img className="title-book-svg" alt="books"

src="images/book.svg"/>

<h3>Search, Find and Buy your book here!</h3>

</div>

<div className="search">

<input id="books" size="20"

className="search-input"

placeholder="Books, Author, Title."/>

<button className="search-button">Search</button>

</div>

<p> Or select your preferred genre</p>

{

error

? error

: loading || !fetched

? 'Loading..'

: (

<select id="genre" className="genre-select">

<option defaultValue>Select a genre</option>

{

genres &&

genres.map(genre => {

return (

<option key={genre}>{genre}</option>)

})

}

</select>

)

}

</div>

);

} export default BookStore

Finally, DEMO !! 👀😅🤞

RxJS + Redux + React = Amazing 😁🤓👏☢️

Just to show a little bit more about the operators, things you could do with RxJS, there is plenty of options, if you want to learn more, check it out here.

Using “delay” to simulate a connection problem, causing an error.

All good so far, now let’s refactor the entire app and finish it up, I will summarize here the steps, but you can check the repository for more details.

Create react components, such as Search/Book/Checkout.

Create a Redux structure folder for actions , reducers, and epic

structure folder for , and Create an Epics to search book and genre .

to and . Create a List book component to display the books searched.

component to display the searched. Add Barrels import strategy to improve the way *.js is imported.

import strategy to improve the way is imported. Create a custom hook to handle the cart logic and basket.

That’s it, let see how all the pieces get together !!

Hope you enjoy it 🤓

You can check out the repo at https://github.com/gbrivate/react-book-store.

Also, check this running at https://gbrivate.github.io/react-book-store/

Don’t forget to install the React Dev Tools and Redux browser plugins, they are very helpful.

Let me know if you have any question, doubt, suggestion, I would love in hear from you!

A note from JavaScript In Plain English

We have launched three new publications! Show some love for our new publications by following them: AI in Plain English, UX in Plain English, Python in Plain English — thank you and keep learning!

We are also always interested in helping to promote quality content. If you have an article that you would like to submit to any of our publications, send us an email at submissions@plainenglish.io with your Medium username and we will get you added as a writer. Also let us know which publication/s you want to be added to.