React: I like RxJS
Tags: react, rxjs, javascript, statemanagement
Published: 2020-11-22 11:44:24

**⚠️ Heads up: ⚠️** This article is an opinion and experiment. I am open to comments and criticisms of this approach. 

## UPDATE: 23 November 2020 

After the tremendous kind and helpful comments I've reworked my initial idea. It's completely changed but so far I think it's and improvement.

I've unintentionally ended up with a very Redux-esque solution. So I think I'm going to call the end of the experiment. :) I have learnt a lot about available options for React and also some new things using RxJS.

Thanks again for all the kind comments and pointers. As well as links to the awesome projects that are up and running.

### useStore Custom Hook

```javascript
import { store$ } from "./store";
import { useEffect, useState } from "react";

function useStore(stateToUse, defaultValue = []) {

    const [ state, setState ] = useState(defaultValue)

    useEffect(() => {
        const sub$ = store$.subscribe(data => {
            setState(data[stateToUse])
        })

        return () =>  sub$.unsubscribe()
    },[stateToUse])

    return state
}

export default useStore
```
<figcaption>Hooks/useStore.js<figcaption>

### Store.js - Central App state

```javascript
import {Subject} from "rxjs";

let AppState = {
    movies: []
}

export const store$ = new Subject();
export const dispatcher$ = new Subject()

dispatcher$.subscribe(data => {
    switch (data.action) {
        case 'GET_MOVIES':
            fetch('http://localhost:5000/movies')
                .then(r => r.json())
                .then(movies => {
                    AppState = {
                        ...AppState,
                        movies
                    }
                    return AppState
                })
                .then(state => store$.next(state))
            break
        case 'CLEAR_MOVIES':
            AppState = {
                ...AppState,
                movies: []
            }
            store$.next( AppState )
            break
        case 'DELETE_MOVIE':
            AppState = {
                ...AppState,
                movies: AppState.movies.filter( movie => movie.id !== data.payload )
            }
            store$.next( AppState )
            break
        case 'ADD_MOVIE':
            AppState = {
                movies: [ ...AppState.movies, data.payload ]
            }
            store$.next( AppState )
            break
        default:
            store$.next( AppState )
            break
    }
})
```
<figcaption>/store.js<figcaption>

Very Redux-like syntax with the added benefit of being able to do Asynchronous actions. Because the store is subscription based it will simply notify any subscriptions of the new state when it arrives.

It might be worth separating states into their own stores, this way a component does not get the entire AppState when the subscription fires `.next()`

### Movie/MovieList.js

```javascript
import React, { useEffect } from 'react'
import MovieListItem from "./MovieListItem";
import { dispatcher$ } from "../store";
import useStore from "../useStore";

const MovieList = () => {

    const movies = useStore('movies' )

    useEffect(() => {
        dispatcher$.next({ action: 'GET_MOVIES' })
    }, [])

    // unchanged JSX.
    return (
        <main>
            <ul>
                { movies.map(movie =>
                    <MovieListItem key={ movie.id } movie={movie} />
                )}
            </ul>
        </main>
    )
}

export default MovieList
```
<figcaption>/Movie/MovieList.js<figcaption>

This component now no longer needs a subscription in an useEffect and simply needs to dispatch the action to get movies. (Very redux-ish). 

### Navbar.js

```javascript
import { dispatcher$ } from "../store";
import useStore from "../useStore";

const Navbar = () => {

    const movieCount = useStore('movies').length

    const onClearMovies = () => {
        if (window.confirm('Are you sure?')) {
            dispatcher$.next({ action: 'CLEAR_MOVIES' })
        }
    }

    return (
        <nav>
            <ul>
                <li>Number of movies { movieCount }</li>
            </ul>
            <button onClick={ onClearMovies }>Clear movies</button>
        </nav>
    )
}

export default Navbar
```
<figcaption>/Navbar/Navbar.js<figcaption>

## END OF UPDATE.
---


#### Source Code:
You can download the source code here:
[React with RxJS on Gitlab](https://gitlab.com/sumodevelopment/react-with-rxjs)

## Introduction
If you are a serious React developer you have no doubt integrated React Redux into your applications. 

React redux offers separation of concerns by taking the state out of the component and keeping it in a centralised place. Not only that it also offers tremendous debugging tools.

This post in no way or form suggests replacing React Redux or The ContextAPI.

## 👋 Hello RxJS

RxJS offers a huge API that provides any feature a developer could need to manage data in an application. I've not ever scratched the surface of all the features. 

In fact, this "experiment" only uses Observables and Subscriptions. 

If you're not familiar with RxJS you can find out more on their official website:

[RxJS Official Documentation](https://rxjs.dev/guide/overview)

## RxJS in React

_I'll be honest, I haven't done a Google search to see if there is already a library that uses RxJS in React to manage state..._

BUT, To use RxJS in React seems pretty straight forward. I've been thinking about doing this experiment for some time and this is that "version 0.0.1" prototype I came up with.

__My main goal is simplicity without disrupting the default flow of React Components.__


## 🤷‍♀️ What's the Problem?

Simply put: _Sharing state_

The problem most beginners face is sharing state between unrelated components. It's fairly easy to share state between parents and child components. Props do a great job. But sharing state between siblings, are far removed components become a little more challenging even in small apps.

As an example, sharing the number of movies in your app between a `MovieList` component and a `Navbar` component. 

The 3 options that I am aware of:

* __Lifting up the state__: Move the component state up to it's parent, which in most cases will be an unrelated component. This parent component now contains unrelated state and must contain functions to update the state.
* __ContextAPI__: Implement the ContextAPI to create a new context and Provide the state to components. To me, This would probably be the best approach for this scenario.
* __React Redux__: Add React Redux to your tiny app and add layers of complexity which in a lot of cases are unnecessary. 

__Let's go off the board for Option number 4.__

## 🎬 React Movies App
I know, cliche, Todo's, Movies, Notes apps. It's all so watered down, but here we are.

### Setup a new Project

I started off by creating a new React project.
```bash
npx create-react-app movies
```

### Components
After creating the project I created 3 components. The components MovieList, MovieListItem and Navbar are simple enough. See the code below.

#### MovieList.js
```javascript
import React, { useState } from 'react'

const MovieList = () => {
    const [ movies, setMovies ] = useState([])
    return (
        <main>
            <ul>
                { movies.map(movie =>
                    <MovieListItem key={ movie.id } movie={movie} /> 
                )}
            </ul>
        </main>
    )
}
export default MovieList
```
<figcaption>Movie/MovieList.js<figcaption>

#### MovieListItem.js
```javascript
const MovieListItem = ({ movie }) => {

    const onMovieDelete = () => {
        // Delete a movie
    }

    return (
        <li onClick={ onMovieDelete }>
            <div>
                <img src={ movie.cover } alt={ movie.title } />
            </div>
            <div >
                <h4>{ movie.title }</h4>
                <p>{ movie.description }</p>
            </div>
        </li>
    )
}

export default MovieListItem
```
<figcaption>Movie/MovieList.js<figcaption>

#### Navbar.js
```javascript
import { useState } from 'react'

const Navbar = () => {
    const [movieCount, setMovieCount] = useState(0)
    return (
        <nav>
            <ul>
                <li>Number of movies { movieCount }</li>
            </ul>
        </nav>
    )
}

export default Navbar
```
<figcaption>Navbar/Navbar.js<figcaption>

The first thing I wanted to do is keep the state management of React. I think it works well in a component and didn't want to disrupt this flow.

Each component can contain it's own state.

### 🔧 Services

I come from an Angular background so the name Services felt like a good choice. 

#### MovieService.js

The service contains a class with static methods to make use of RxJS Observables. 

```javascript
import { BehaviorSubject } from 'rxjs'

class MovieService {
    static movies$ = new BehaviorSubject([])

    static getMovies() {
        fetch('http://localhost:3000/movies')
            .then(r => r.json())
            .then((movies) => this.setMovies(movies))
    }

    static setMovies(movies) {
        this.movies$.next(movies)
    }

    static deleteMovie(id) {
        this.movies$.next(this.movies$.value.filter(movie => movie.id !== id))
    }

    static clearMovies() {
        this.movies$.next([])
    }
}


export default MovieService
```
<figcaption>Services/MovieService.js<figcaption>

This MovieService uses static methods to avoid me having to manage a single instance of the service. I did it this way to keep it simple for the experiment. 

### 🛠 Integrating the Service into the MovieList component.

I did not want to alter the way React components work, specifically how they set and read state.

Here is the MovieList component using the Service to get and set the movies from a local server.

```javascript
import React, { useEffect, useState } from 'react'
import MovieService from "../Services/Movies"
import MovieListItem from "./MovieListItem";

const MovieList = () => {
    
    // Keep the way a component uses state.
    const [ movies, setMovies ] = useState([])
    
    // useEffect hook to fetch the movies initially.
    useEffect(() => {
        // subscribe to the movie service's Observable.
        const movies$ = MovieService.movies$.subscribe(movies => {
            setMovies( movies )
        })
        
        // Trigger the fetch for movies.
        MovieService.getMovies()
        
        // Clean up subscription when the component is unmounted.
        return () => movies$.unsubscribe()

    }, [])

    // unchanged JSX.
    return (
        <main>
            <ul>
                { movies.map(movie => 
                    <MovieListItem key={ movie.id } movie={movie} /> 
                )}
            </ul>
        </main>
    )
}

export default MovieList
```
<figcaption>Movie/MovieList.js - with Service<figcaption>

### Accessing data in an unrelated component 

At this point, the movie data is stored in the Observable (BehaviorSubject) of the MovieService. It is also accessible in any other component by simply subscribing to it.

#### Navbar - Getting the number of movies

```javascript
import { useEffect, useState } from 'react'
import MovieService from "../Services/Movies"

const Navbar = () => {

    const [movieCount, setMovieCount] = useState(0)

    useEffect(() => {
        // subscribe to movies
        const movies$ = MovieService.movies$.subscribe(movies => {
            setMovieCount( movies.length )
        })
        return () => movies$.unsubscribe()
    })

    return (
        <nav>
            <ul>
                <li>Number of movies { movieCount }</li>
            </ul>
        </nav>
    )
}

export default Navbar
```
<figcaption>Navbar/Navbar.js - with Service<figcaption>

The default flow of the component remains unchanged. The benefit of using the subscriptions is that only components and its children subscribing to the movies will reload once the state updates.

### 🗑 Deleting a movie:

To take this a step further, we can test the subscriptions by create a delete feature when a movie is clicked.

#### Add Delete to the MovieListItem Component

```javascript
import MovieService from "../Services/Movies";
import styles from './MovieListItem.module.css'

const MovieListItem = ({ movie }) => {

    // Delete a movie.
    const onMovieDelete = () => {
        if (window.confirm('Are you sure?')) {
            // Delete a movie - Subscription will trigger
            // All components subscribing will get newest Movies.
            MovieService.deleteMovie(movie.id)
        }
    }

    return (
        <li onClick={ onMovieDelete } className={ styles.MovieItem }>
            <div className={ styles.MovieItemCover }>
                <img src={ movie.cover } alt={ movie.title } />
            </div>
            <div className={ styles.MovieItemDetails }>
                <h4 className={ styles.MovieItemTitle }>{ movie.title }</h4>
                <p>{ movie.description }</p>
            </div>
        </li>
    )
}

export default MovieListItem
```
<figcaption>Movie/MovieListItem.js - Delete a movie<figcaption>

This change above is very simple. None of the other components needs to change and will received the latest state because it is subscribing to the Service's BehaviorSubject.


### 👨🏻‍🏫 What I learnt?

Well, there are many ways to skin a cat. The main drawback of using this approach is sacrificing the React Redux DevTools. If an application grows I have a suspicion all the subscriptions could become cumbersome and hard to debug. 

Tools like RxJS Spy could be a solution to keep track and debug your code. 

### Simplicity
I do enjoy the simplicity of this approach and it doesn't currently disrupt the default React features but tries to compliment them.

📝 I'd love to heard from you guys and get some opinions, both positive and negative. 

📖 __Thanks for reading!__