React with Facades + RxJS

Thomas Burleson
Jan 14 · 8 min read

State Management

Applications often (a) need to share or reuse data and (b) have many events to change data. State management allows us to centralize data changes and control the insanity.

Developers often approach state management with reluctance, fear, and a naive understanding of when and how to use it.

Harry Beckwith and Thomas Burleson decided to write this tutorial to demonstrate real-world, best-practices when using state management in React.

State management is important when data:

  1. Must be shared between components,
  2. Must be accessible independent of view instances, and
  3. Changes to the data should be centralized
  4. Changes notifications should performantly update views

Redux Pattern

The Redux pattern is widely recognized as an important choice to manage state changes. Unfortunately, implementations of the Redux pattern (eg ReduxJS) often leads to lots of cruft (large numbers of files).

Let’s use a better solution (and avoid the cruft)!

Akita for State Management.

Akita is a relatively quiet newcomer — which uses the Flux/Redux patterns — that is making a huge splash in the RxJS + State Management waters. Akita enables us to easily build reactive, asynchronous, data-push solutions for our state management needs.

With data-push architectures, view components simply react to asynchronous data change notifications and render the current data values.

Akita is a state management pattern, built on top of RxJS, which combines the idea of multiple data stores from Flux, the immutable updates from Redux, and the power of streaming data to create the Observable Data Stores model.

more about Akita…


Push-based Architectures:

Here are some more articles for those developers interested in learning more about RxJS and push-based architectures:


Tutorial

In this tutorial, we will show developers how to implement and use a reactive Facade with internal State Management features.

The magic with our approach is (a) how quickly and easily we implemented these features with minimal code and (b) how we integrated those features into our views with simple, terse code.

Let’s build a Todo application with powerful state management features.

In this application we will be able to easily:

  • add/remove todos,
  • toggle ‘completed’ status
  • filter the list
  • undo/redo any changes

Using the classic Todo application, let’s implement a React version that uses state management, facades, and hooks.

Since the view components are not the focus of this tutorial, we have already prepared a CodeSandbox starting point: React Tutorial Start

We recommend that developers fork the starting sandbox and implement the solution code while reading this tutorial!


Step #1: View components

Here is the already-implemented view source that enables all these features:

What developers should note is that all the business logic is hidden inside the facade. See how clean the React UI components are with just property bindings and event delegation?

The facade handles the user interactions (aka events):

<AddTodo onAdd={(item) => facade.addTodo(item)} />

The facade also publishes properties for use in the view:

<TodoList todos={facade.todos} />

Notice that the facade only publishes two properties: todos and filter that are used in view data bindings. Let’s examine and improve upon the facade; we will come back to this TodosPage component once our Facades and Hooks are ready!


Step #2: Facades

Using the classic Todo application, let’s implement a React version that uses state management, facades, and hooks.

As the diagram shows, the facade hides [from the views] the use of TodoService and the TodoStore. The facade could used directly or we can prepare a custom hook useTodosHook() that interacts with the facade.

For our TodoStore we will internally use Akita to

  • manage 1-way data flows
  • optimize stream emissions for data changes, and
  • leverage built-in undo/redo features

We will also use ImmerJS to

  • ensure that all data pushed to the view layers is immutable
  • allow easy state updates inside the Facade.

In our tutorial we are not persisting the Todo list; we do not need data services. So let’s modify the diagram to be clear:

The Naive Facade

Now if we look at the current TodoFacade implementation, we see the methods are empty and we will implement those soon.

Even more problematic, however, are the todos and filter properties. These expose raw values to external observers.

We should never publish raw values!! This is not reactive. Raw values can be consider snapshots in time. If the values later change, the views must poll/requery for those values.

Question: How do existing view instance know that the facade values have changed?
Answer: With RxJS streams, we can push new values to observers at any time in the future. Streams are long-lived, data-push connections.


Step #3: TodosStore

Now we could use a ‘home-grown’ approach and write [in our Facade] a significant amount of CRUD code for our Todo collection, reducer, and performant RxJS stream emissions.

Instead let’s use Akita to manage our state (todos + filter)… it is so easy!

If we use TodosStore extends EntityStore then our TodosStore will have built-in functionality for CRUD operations on the todo collection. Then we just add a filter property to our TodosState state.

The Akita store has functionality to update and cache our state (aka data). But it should not be used to read or access that data!

The Akita store is used only for incoming data. For outgoing data (eg to read/access the state), we use a TodosQuery: with two (2) public property streams: filter$ and todos$.

The filter$ is a stream constructed from this.select(...) which uses the predicate function state => state.filter to extract the filter value from our cached state. This stream is special:

  • Whenever the filter state data changes, filter$ will re-emit the value from the stream.
  • If any other state data changes — but NOT the filter value - the filter$ stream will not re-emit. The value is memoized!

To publish a filtered list of Todo(s), combine the output of the two (2) streams. We use this.selectAll() to get a stream for the entire todo list (regardless of completed status) and combine that stream that with the filter$ stream.

The values from BOTH streams are used to return a final list of only the desired todo items.

Finally, we export ^ the store and query instances… for use in the TodoFacade.


Step #4: Improved TodosFacade

The Facade has a clear, intuitive reactive API (properties are streams, methods are incoming only).

Notice how the facade maintains a 1-way data flow. Incoming changes are passed into the store. And immutable data is maintained with:

this.store.update(state => ({...state, filter}) );

In this case, immutable means that any property change will require a new instance of the property container. This enables === to be used to quickly determine if the state has changed in any part.

And we even use the power of the StateHistoryPlugin to create a history instance that supports undo/redo.


Step #5: Custom useTodosHook

Since our Facade is publishing streams (filter$ and todos$), let’s use the powerful useObservable() hook to subscribe, extract values, and auto-unsubscribe from the RxJS streams.

Here ^ we define a custom Tuple TodoHookTuple that defines the array returned from our custom hook.

Any time the filter or todos value changes, our custom hook returns the current values and auto-rerender the owning view.


Step #6: Updated TodosPage View

Finally, our TodosPage functional component simply needs to use our custom hook:

The beauty of the Tuple is that we can destructure parts of the response to any variable names we want. We could even ignore parts:

const [_, todos] = useTodosHook()

Now everything should work like magic!


Step #7: Stop Mutation Bugs

Earlier, we stated that the Facade maintains immutable data; eg

updateFilter(filter: VISIBILITY_FILTER) {
this.store.update( state => ({ ...state, filter }));
}

For immutability, we used the spread operator ^ to create a new state instance. But this does not mean that the state is write-protected!

A view component could do this:

todo.completed = !todo.completed

If this is allowed, it means that a todo item can be modified OUTSIDE our state management… and the Facade + Store would never announce those changes. 😰


Immer

We can use produce() from ImmerJS to write-protect our state; while still enabling the facade to easily update state properties.

import { produce } from 'immer';export class TodosFacade {  updateFilter(filter: VISIBILITY_FILTER) {
this.store.update( produce((draft:TodosState) => {
draft.filter = filter;
}));
}
}

Here ^ produce(...) will provide a full, write-enabled draft of our state.

  • We are not using complicated, nested spread operators.
  • We can modify any parts directly.

Immer will update the final state as an immutable, write-protected version. After you implement the immer changes in your version, try out your Todo application again.

Click the Mutate Status button and you will see this RTE:


Final Solution

We now have a Todo application with state management, write-protected state, undo/redo, a Facade with a Todo API, and a custom hook.

True sweetness! 🙇 Huge thanks to Harry Beckwith for (a) exposing the need for this post and for his co-authorship!

Here is a full-working version of the tutorial:

Thomas Burleson

Written by

Solution Architect with passion for React, Angular, UX, and great software products. Formerly a Google Team Lead for AngularJS Material, @angular/flex-layout, …

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade