React Use Sub: A Lightweight Subscription-Based Store for React
Introduction
The ‘react-use-sub’ library is a lightweight and efficient way to manage state in your React applications. It allows you to create a subscription-based store that can be easily integrated into your existing codebase. This library is designed to be simple to use, easy to test, and has no dependencies. It also supports TypeScript and is very small in size, making it ideal for performance-critical applications.
Installation
You can install ‘react-use-sub’ using npm or yarn:
Using npm
npm install react-use-sub
Using yarn
yarn add react-use-sub
Basic Usage
To use ‘react-use-sub’, you need to create a store and then subscribe to it in your components. Here is a simple example:
// store.js
import { createStore } from 'react-use-sub';
const initialState = { foo: 'bar', num: 2 };
export const [useSub, Store] = createStore(initialState);
// component.js
import { useSub } from '/path/to/store.js';
export const App = () => {
const { fooLength, num } = useSub(({ foo, num }) => ({ fooLength: foo.length, num }));
const square = useSub(({ num }) => num**2);
return <div>Magic number is: {fooLength * num * square}</div>;
}
In this example, we create a store with an initial state and then subscribe to it in the App
component. We use the useSub
hook to subscribe to the store and get the values we need.
Advanced Usage
Conditional Updates
When setting the store, you can specify conditions to update only if certain values change. For example:
Store.set(({ articles }) => (articles.length ? { stock: articles.length } : undefined));
Shallow Equality Optimization
The library performs shallow equality checks to determine if a rerender is necessary. This means that if the value of foo
does not change, the component will not rerender.
Multiple Subscriptions in a Single Component
You can subscribe to multiple values in a single component:
export const GreatArticle = () => {
const { id, author, title } = useSub(({ article }) => article);
const reviews = useSub(({ reviews }) => reviews);
const [trailer, recommendation] = useSub(({ trailers, recommendations }) => [trailer[id], recommendations[id]]);
return <>...</>;
}
Multiple Store Updates
You can perform multiple store updates in a single synchronous task without causing unnecessary rerenders:
Store.set({ foo: 'bar' });
Store.set({ num: 2 });
Store.set({ lastVisit: new Date() });
Multiple Stores
You can create multiple stores and subscribe to them separately:
import { createStore } from 'react-use-sub';
export const [useArticleSub, ArticleStore] = createStore(initialArticleState);
export const [useCustomerSub, CustomerStore] = createStore(initialCustomerState);
export const [useEventSub, EventStore] = createStore(initialEventState);
Persisting Data on the Client
You can persist data on the client using local storage or other methods:
const usePersistArticles = () => {
const articles = useSub(({ articles }) => articles);
useEffect(() => {
localStorage.setItem('articles', JSON.stringify(articles));
}, [articles]);
};
const localStorageArticles = localStorage.getItem('articles');
const initialState = {
articles: localStorageArticles ? JSON.parse(localStorageArticles) : {},
}
const [useSub, Store] = createStore(initialState);
Middlewares
You can write custom middlewares to handle special state updates:
import { createStore, StoreSet } from 'react-use-sub';
type State = { conversionStep: number };
const initialState: State = { conversionStep: 1 };
const [useSub, _store] = createStore<State>(initialState);
const set: StoreSet<State> = (update) => {
const prevState = _store.get();
_store.set(update);
const state = _store.get();
if (prevState.conversionStep !== state.conversionStep) {
trackConversionStep(state.conversionStep)
}
}
const Store = { ..._store, set, reset: () => _store.set(initialState) };
export { useSub, Store };
Testing
You can test your store using the ‘test-util’ package:
import 'react-use-sub/test-util';
describe('<MyExample />', () => {
it('renders the stock', () => {
// initialization
Store.set({ article: { stock: 1337 } as any });
// render with stock 1337
const { container } = render(<MyExample />);
expect(container.textContent).toBe('Article stock is: 1337');
// update the stock
Store.set({ article: { stock: 444 } as any });
expect(container.textContent).toBe('Article stock is: 444');
});
});
SSR
For SSR, you can create a store instance that is provided by a React context:
import React, { useMemo } from 'react';
import { createStore, StoreType, UseSubType } from 'react-use-sub';
const initialState = { foo: 'bar', num: 2 };
type State = typeof initialState;
const Context = React.createContext<{ useSub: UseSubType<State>; store: StoreType<State> }>({} as any);
export const useStore = (): StoreType<State> => React.useContext(Context).store;
export const useSub: UseSubType<State> = (...args) => React.useContext(Context).useSub(...args);
export const StoreProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const value = useMemo(() => {
const [useSub, store] = createStore(initialState);
return { useSub, store };
}, []);
return <Context.Provider value={value}>{children}</Context.Provider>;
};
Conclusion
The ‘react-use-sub’ library is a powerful tool for managing state in your React applications. It is lightweight, easy to use, and supports TypeScript. With its subscription-based approach, you can create complex applications with ease. Whether you are building a simple to-do list or a complex e-commerce application, ‘react-use-sub’ can help you manage your state efficiently.