SkillAgentSearch skills...

ReSub

A library for writing React components that automatically manage subscriptions to data sources simply by accessing them

Install / Use

/learn @microsoft/ReSub
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

ReSub

GitHub license npm version npm downloads Build Status David David

A library for writing better React components and data stores. Uses automatic subscriptions to reduce code and avoid common data flow pitfalls. Scales for projects of all sizes and works great with TypeScript.

ReSub v2

For the 2.x+ ReSub releases, to get in line with the apparent future of React, we have changed how ReSub uses the React lifecycle functions to instead use the getDerivedStateFromProps path. See this link for more details on the new lifecycle. This means that, for the standard happy path of just using _buildState, everything should keep working just like it used to. However, if you were using the old React lifecycle functions (componentWillMount, componentWillReceiveProps, etc.) in any way, you will likely find those functions not being called anymore. This change should future-proof ReSub for a while, and play more nicely with the future async rendering path that React is moving toward.

As a small secondary note, for 2.x+, we also removed support for the old-school manual subscriptions. We strongly suggest moving to either autosubscriptions (where ReSub's true differentiating value is) or, if you prefer manual subscriptions, directly subscribe to the stores in your component using whatever lifecycle you prefer.

All in all, for more complicated projects especially, these may be substantial changes to your usage of ReSub, and upgrading may be hard. You're obviously welcome to keep using the 1.x branch of ReSub indefinitely, and if there are bugs, please let us know and we will attempt to fix them, but we won't be putting any more energy into features/examples/etc. on 1.x and should consider it to be on LTS at this point, since it doesn't work with versions of React newer than 16.8.6.

Overview

In React’s early days, Flux gave us guidance on how to manage data flow in our apps. At its core, data would be placed into stores and React components would fetch it from them. When a store’s data was updated, it would notify all concerned components and give them the opportunity to rebuild their states.

While Flux works well, it can also be cumbersome and error prone. Separate actions, action creators, and stores can result in a great deal of boilerplate code. Developers can fetch data from a store but fail to subscribe to changes, or components can oversubscribe and cause performance issues. Furthermore, developers are left to implement these patterns from scratch.

ReSub aims to eliminate these limitations (and more) through the use of automatic data binding between stores and components called autosubscriptions. By using TypeScript’s method decorators, ReSub components can subscribe to only the data they need on only the stores that provide it, all without writing any code. ReSub works with both traditional class components as well as function components, which is the direction that React seems to be heading for most usage.

Basic Example

The easiest way to understand ReSub is to see it in action. Let’s make a simple todo app.

The heavy lifting in ReSub is done mostly within two classes, ComponentBase and StoreBase. It’s from these that we make subclasses and implement the appropriate virtual functions.

First, we create a store to hold todos:

import { StoreBase, AutoSubscribeStore, autoSubscribe } from 'resub';

@AutoSubscribeStore
class TodosStore extends StoreBase {
    private _todos: string[] = [];

    addTodo(todo: string) {
        // Don't use .push here, we need a new array since the old _todos array was passed to the component by reference value
        this._todos = this._todos.concat(todo);
        this.trigger();
    }

    @autoSubscribe
    getTodos() {
        return this._todos;
    }
}

export = new TodosStore();

Next, we create a component to display the todos:

import * as React from 'react';
import { ComponentBase } from 'resub';

import TodosStore = require('./TodosStore');

interface TodoListState {
    todos?: string[];
}

class TodoList extends ComponentBase<{}, TodoListState> {
    protected _buildState(props: {}, initialBuild: boolean, incomingState: {} | TodoListState): TodoListState {
        return {
            todos: TodosStore.getTodos()
        }
    }

    render() {
        return (
            <ul className="todos">
                { this.state.todos.map(todo => <li>{ todo }</li> ) }
            </ul>
        );
    }
}

export = TodoList;

That’s it. Done!

When future todos are added to the TodoStore, TodoList will automatically fetch them and re-render. This is achieved because TodoList._buildState makes a call to TodosStore.getTodos() which is decorated as an @autoSubscribe method.

Subscriptions and Scaling

ReSub is built with scalability in mind; it works for apps of all sizes with all scales of data traffic. But this doesn’t mean scalability should be the top concern for every developer. Instead, ReSub encourages developers to create the simplest code possible and to only add complexity and tune performance when it becomes an issue. Follow these guidelines for best results:

  1. Start by doing all your work in _buildState and rebuilding the state from scratch using autosubscriptions. Tracking deltas and only rebuilding partial state at this stage is unnecessary for the vast majority of components.
  2. If you find that components are re-rendering too often, introduce subscriptions keys. For more information, see the “Subscriptions by key” and “Subscriptions by props” sections below.
  3. If components are still re-rendering too often, consider using trigger throttling and trigger blocks to cut down on the number of callbacks. For more information, see the “Trigger throttling” and “Trigger blocks” sections below.
  4. If rebuilding state completely from scratch is still too expensive, manual subscriptions to stores (store.subscribe()) with callbacks where you manage your own state changes may help.

A Deep Dive on ReSub Features

Subscriptions and Triggering

Subscriptions by key:

By default, a store will notify all of its subscriptions any time new data is available. This is the simplest approach and useful for many scenarios, however, stores that have heavy data traffic may result in performance bottlenecks. ReSub overcomes this by allowing subscribers to specify a string key that limit the scope in which they will trigger.

Consider an example where our Todo app differentiates between high and low priority todo items. Perhaps we want to show a list of all high priority todo items in a HighPriorityTodoItems component. This component could subscribe to all changes on the TodosStore, but this means it’d re-render even when a new low priority todo was created. That’s wasted effort!

Let’s make TodosStore smarter. When a new high priority todo item is added, it should trigger with a special key TodosStore.Key_HighPriorityTodoAdded instead of using the default StoreBase.Key_All key. Our HighPriorityTodoItems component can now subscribe to just this key, and its subscription will trigger whenever TodosStore triggers with either TodosStore.Key_HighPriorityTodoAdded or StoreBase.Key_All, but not for TodosStore.Key_LowPriorityTodoAdded.

All of this can still be accomplished using method decorators and autosubscriptions. Let’s create a new method in TodosStore:

class TodosStore extends StoreBase {
    ...

    static Key_HighPriorityTodoAdded = "Key_HighPriorityTodoAdded";

    @autoSubscribeWithKey(TodosStore.Key_HighPriorityTodoAdded)
    getHighPriorityTodos() {
        return this._highPriorityTodos;
    }
}

Note: Of course it’s possible to separate high and low priority todo items into separate stores, but sometimes similar data is simultaneously divided on different axes and is therefore difficult to separate into stores without duplicating. Using custom keys is an elegant solution to this problem.

Autosubscriptions using @key:

Key-based subscriptions are very powerful, but they can be even more powerful and can reduce more boilerplate code when combined with autosubscriptions. Let’s update our TodosStore to add the @key decorator:

class TodosStore extends StoreBase {
    ...

    @autoSubscribe
    getTodosForUser(@key username: string) {
        return this._todosByUser[username];
    }
}

Now, we can establish the autosubscription for this user in _buildState:

class TodoList extends ComponentBase<TodoListProps, TodoListState> {
    ...

    protected _buildState(props: {}, initialBuild: boolean, incomingState: {} | TodoListState): TodoListState {
        return {
            todos: TodosStore.getTodosForUser(this.props.username)
        }
    }
}

_buildState will be called when TodoStore triggers any changes for the specified username, but not for any other usernames.

Compound-key subscriptions/triggering

Sometimes, either when a single store contains hierarchical data, or when you have more than one parameter to a function that you'd like to have key-based subscriptions to (i.e. a user and a name of an object that the user has), the

View on GitHub
GitHub Stars605
CategoryContent
Updated7mo ago
Forks48

Languages

TypeScript

Security Score

87/100

Audited on Sep 9, 2025

No findings