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.
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?
To be more practical, let's implement a classic TODO List example using ReactJS and RXJS.
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;
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;
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;
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;