Coder Social home page Coder Social logo

thomasburleson / todo-rsm-react Goto Github PK

View Code? Open in Web Editor NEW
5.0 4.0 1.0 541 KB

React Todo application using Reactive State Management (RSM) with Akita + Facades

HTML 3.25% CSS 17.48% SCSS 4.34% TypeScript 65.84% JavaScript 9.08%
rsm akita react state-management reactive-stores

todo-rsm-react's Introduction

TodoMVC (React) using Facades + Akita

Introduction

React solutions using Redux are often complex and difficult to maintain. Developers can use RxJS to build push-based APIs... and create clean, easily understood, easily maintained architectures.

For a deep-dive into the architecture and considerations, read React Facades - Best Practices




Todo MVC

Let's create a simple Todo application written in React + TypeScript. This application is architected using react hooks, facades, RxJS, and Akita state management. With Akita, we also get powerful undo/redo features.

A live demo is available at: https://codesandbox.io/s/react-todo-akita-final-u6gx3

This project was created with npx create-react-app todo-akita --template typescript




Why Facades + Akita + RxJS

When our todos and filter state changes, we need to trigger UI re-renders (to show the currents states values for those properties/state.

I could have used MobX to provide notifications to state changes, instead I am using RxJS to expose the state and long-lived streams. To keep our code super clean, we use a special hook to subscribe to the RxJS observables/streams: useObservable().

The useObservable() hook will emit the current state values whenever the stream emits updated, changed values. Even better - using useObservable within the custom hook useTodosHook - I am able to hide all details of RxJS use and subscription management.

Note that I also use:

  • Akita to manage state collection data (list of Todo items), and
  • Facade pattern to encapsulate the use of Akita and expose a clean API + 'smart' view model for the UI components.

Learn more about Facades here: State Management in React w/ Facades & RxJS




Benefits

Implementing distinct business layers (using Facades and Hooks) has myrida benefits:

  • encourages 1-way data flows,
  • promotes the use of Presentation components,
  • enables UI-independent testing of the business layer(s).

1-way Data flows

The benefits of using hooks + facades is a super clean view layer AND event delegation to the facade. This maintains a 1-way data flow.

  • The view components delegate user interactions to the Facade.
  • The view components render data output from the Facade.
  • The custom hook useTodosHook() hides the Facade's use of RxJS streams.
  • The Facade pushes data updates/changes to the view using RxJS streams

Super-Clean UI Components

With facades ( + hooks), the UI components do not have any business logic, state management, nor data persistance.

This also means that the TodosPage is using presentational children components. Each child component output events to the parent business container TodoPages; which - in turn - delegates directly to Facade methods.




Super easy Testing

The best way to test an application is use a layered-testing approach:

  • Use Jest for rigorous testing of all non-UI components: the business and data-service layers
  • Use Cypress for UI and user-interaction testing.

Jest Testing


If we consider the business and data-access layers the 'engine' for our application, then we should be able to easily change the UI if the engine is fully tested and stable.

image

For our business layers we have three (3) major components:

  • TodosStore + TodosQuery (Akita State Management)
  • TodosFacade: API and properties (RxJS streams)
  • TodosHook: Custom React Hook (UI Component State + Rendering)

The MORE rigorous our testing of the business layer, the MORE confident we become regarding the data flows and core of the application. In our current scenario:

  • the Store and Query do NOT need be tested; as the logic is super simple.
  • the Facade and Hook will, however, be extensively tested.

Testing with RxJS Streams


Our TodosFacade has two (2) published streams: todos$ and filter$. We will use an RxJS utility readFirst() to extract a single value from the stream.

it('instance shouild be initialized properly', async () => {
  const facade = makeFacade();

  expect(facade).not.toBeNull();

  const todos = await readFirst(facade.todos$);
  const filter = await readFirst(facade.filter$);

  expect(todos.length).toBe(0);
  expect(filter).toBe(VISIBILITY_FILTER.SHOW_ALL);
  expect(facade.history).not.toBeNull();
});

๐Ÿ‘‰ @see todos.facade.spec.ts


Testing React Hooks


The nature of React Hooks requires testing to be performed in the context of a UI component. Let's use @testing-library/react-hooks to make our testing clean and succinct.

Let's use renderHook() to get access to the mutable response from the custom hook. And we will also use the act() utility to encapsulate and perform 1..n actions that trigger hooks and UI updates.

it('should emit updated todos after addTodo()', () => {
  const { hookResponse } = renderHook(useTodosHook);

  const facade = (): TodosFacade => hookResponse.current[2];
  const todos = (): Todo[] => hookResponse.current[1];
  const filter = (): VISIBILITY_FILTER => hookResponse.current[0] as VISIBILITY_FILTER;

  expect(facade()).toBeTruthy();
  expect(todos().length).toBe(0);
  expect(filter()).toEqual(VISIBILITY_FILTER.SHOW_ALL);

  act(() => {
    facade().addTodo('Task 1');
    facade().addTodo('Task 2');
  });

  expect(todos().length).toBe(2);
});

Recall that our custom hook useTodosHook returns a Tuple response:

export type TodoHookTuple = [string, Todo[], TodosFacade];

To simplify access within the response Tuple object, I create accessor functions (eg filter()) to the actual properties values.

๐Ÿ‘‰ @see todos.hook.spec.ts

Testing hooks is now super easy, clean, and fun!




Immutability

Great architectures with 1-way data flows must use immutable data to centralize state changes into a single area. Using ImmerJS to emit immutable data outputs and enable easy mutations inside the Facade or Store.

Use the produce() function to simultaneously mutate data and return an updated, immutable object.

import { produce } from 'immer';

export class TodosFacade {
  constructor(private store: TodosStore, private query: TodosQuery) {}

  updateFilter(filter: VISIBILITY_FILTER) {
    this.store.update(
      produce((draft: TodosState) => {
        draft.filter = filter;
      })
    );
  }
}

todo-rsm-react's People

Contributors

thomasburleson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

urgency-dev

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.