Skip to content
Simple example of RxJS usage with ReactJS Hooks
JavaScript HTML CSS
Branch: master
Clone or download
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
public Add souce code Nov 5, 2018
src Optimize rxjs imports Nov 6, 2018
.env Create .env Nov 5, 2018
.gitignore Create .gitignore Nov 5, 2018
README.md Update README.md Nov 6, 2018
package.json Add bundle analyzer Nov 6, 2018
yarn.lock Update yarn lock Nov 6, 2018

README.md

ReactJS 16.7 Hooks + RxJS

The ReactJS hooks feature introduced by the first 16.7 alpha release is being considered the new "state-of-the-art" of the React. The goal is to provide a more simple way to manage component’s internal state and lifecycle as an alternative to class components.

Adventurers javascript developers may wish to use RXJS to go full reactive instead of redux. But who try to use pure RXJS and React faces a boring boilerplate: subscribing and unsubscribing to observables and setting changes to component internal state, for EVERY connected component.

import React, { Component } from 'react';
import fooStore from './fooStore';

class Foo extends Component {

  state = {
    name: ''
  };
  
  componentWillMount() {
    this.subscription = fooStore.name.subscribe(name => this.setState({name}));
  }
  
  componentWillUnmount() {
    this.subscription.unsubscribe();
  }
  
  [...]

}

Some developers appeal to decorators to avoid this, but this is one of the known causes of the “component tree wrapper hell” that mobilized the React Team to introduce the hooks feature.

They also appeal to other libraries like “Recycle JS” that abstracts away the observable subscription/unsubscription.

The magical hook

No more extra libraries or decorators. Just one hook and your components are ready to react to any RXJS Observable changes:

import {useState, useEffect} from 'react';

function useObservable(observable, initialValue) {
  const [value, setValue] = useState(initialValue);

  useEffect(() => {
    const subscription = observable.subscribe(newValue => {
      setValue(newValue);
    });
    return () => subscription.unsubscribe();
  }, []);

  return value;
}

function FooComponent() {
  
  const value = useObservable(fooObservable, 'Hello!');
  
  // render it :)
  
}

It abstracts away the observable subscription and unsubscription and subscribes to observable changes updating it to component internal state, pretty simple huh?

Task list example

To be more practical, let's implement a classic TODO List example using ReactJS and RXJS.

Task store

Here we control the task list state as an application state.

import { BehaviorSubject } from "rxjs/internal/BehaviorSubject";

const taskStore = {
  __sequence: 0,
  __tasks: [],

  tasks: new BehaviorSubject([]),

  add(task) {
    task.id = taskStore.__sequence++;
    taskStore.__tasks = [...taskStore.__tasks, task];
    taskStore.tasks.next(taskStore.__tasks);
  },

  switchDone(id) {
    taskStore.__tasks = taskStore.__tasks.map(task => {
      if (task.id === +id) {
        return {
          ...task,
          done: !task.done
        };
      }
      return task;
    });
    taskStore.tasks.next(taskStore.__tasks);
  },

  remove(id) {
    taskStore.__tasks = taskStore.__tasks.filter(task => task.id !== +id);
    taskStore.tasks.next(taskStore.__tasks);
  }
};

export default taskStore;

Task list component

Component to display the task list and trigger the "switch done/undone" and "remove" actions to parent component.

import React from "react";

function TaskList({ tasks = [], onSwitch, onRemove }) {

  function handleSwitchClick(e) {
    if (typeof onSwitch === "function") {
      const { name: taskId } = e.currentTarget;
      onSwitch(+taskId);
    }
  }
  
  function handleRemoveClick(e) {
    if (typeof onRemove === "function") {
      const { name: taskId } = e.currentTarget;
      onRemove(+taskId);
    }
  }

  return (
    <ul>
      {tasks.map(task => (
        <li
          key={task.id}
          style={{ textDecoration: task.done ? "line-through" : "unset" }}
        >
          <span>{task.description}</span>
          <button 
            name={task.id} 
            type="button" 
            onClick={handleSwitchClick}
          >
            {task.done ? "Undone" : "Done"}
          </button>
          <button 
            name={task.id} 
            type="button" 
            onClick={handleRemoveClick}
          >
            Remove
          </button>
        </li>
      ))}
    </ul>
  );
}

export default TaskList;

Task form component

Component to provide a "description" field and a "submit" event.

import React from "react";

function TaskForm({ onSubmit }) {
  const [description, setDescription] = useState("");

  function handleDescriptionChange(e) {
    setDescription(e.target.value);
  }

  function handleSubmit(e) {
    e.preventDefault();
    setDescription("");
    if (typeof onSubmit === "function") {
      onSubmit({ description, done: false });
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        name="description"
        onChange={handleDescriptionChange}
        value={description}
      />
      <button type="submit">Add</button>
    </form>
  );
}

export default TaskForm;

App component

Finally, the App component, where everything are controlled.

import React from "react";
import TaskForm from './task/TaskForm';
import TaskList from './task/TaskList';
import taskStore from './task/taskStore';

function App() {
  const tasks = useObservable(taskStore.tasks, []);

  return (
    <div>
      <TaskForm onSubmit={taskStore.add} />
      <TaskList 
        tasks={tasks} 
        onSwitch={taskStore.switchDone} 
        onRemove={taskStore.remove}
      />
    </div>
  );
}

export default App;
You can’t perform that action at this time.