react-containerized-state
v1.1.1
Published
Fast and minimal state container which can be used and shared across React or non-React components.
Downloads
12
Maintainers
Readme
react-containerized-state
Fast and minimal state container which can be used and shared across React or non-React components.
Using it with React components is objectively better than built-in useState
React hook due to the removal of unnecessary renders. It is optimized in a way that only the components that need the container's value (via useValue()
hook) are rendered upon state change.
Installation
To install the package, run:
npm install react-containerized-state
# Or via any other package manager
Basic usage
Consider the following example:
import { createStateContainer } from "react-containerized-state";
// You can move this container to a separate module and share it across your app
const counter = createStateContainer(0);
const Controls = () => {
const updateCount = counter.useUpdateValue();
const increase = (n: number) => {
updateCount(c => c + n);
};
return (
<div>
<button onClick={() => increase(1)}>Increase by 1</button>
<button onClick={() => increase(5)}>Increase by 5</button>
<button onClick={() => increase(10)}>Increase by 10</button>
</div>
);
};
const Display = () => {
const count = counter.useValue();
return <div>Count: {count}</div>;
};
const Container = () => {
return (
<div>
<h2>Container</h2>
<Controls />
<Display />
</div>
);
};
const Page = () => {
return (
<main>
<Container />
</main>
);
};
export default Page;
In this example, when the user clicks on the buttons of the Controls
component, the state changes and each component that is subscribed to the container via useValue
hook will be notified and re-rendered as a result. In other words, only the Display
component re-renders on state change.
You can also use this container with non-React components as well. For example we can add this line to the example above outside of any React component:
// Logs the new value on state change
counter.subscribe(console.log);
setTimeout(() => {
counter.updateValue(100);
}, 2000);
This way, after 2 seconds the value of counter
container updates to 100
resulting a state dispatch which calls all the subscribers of the container (including those in the React components).
Usage with selectors
There may be situations where you have to store a complex state in your container (It is recommended to have different small containers instead of several large ones). In these cases, you may not want to subscribe to all the fields of the complex state. Instead you want to subscribe to different parts in different components or modules.
Consider the following example:
import { createStateContainer } from "react-containerized-state";
// You can move this container to a separate module and share it across your app
const complexState = createStateContainer({ a: 1, b: "2" });
const Controls = () => {
const updateState = complexState.useUpdateValue();
return (
<div>
<button
onClick={() => {
updateState(s => ({
...s,
a: s.a + 1,
}));
}}
>
Update State.A
</button>
<button
onClick={() => {
updateState(s => ({
...s,
b: String(Number(s.b) + 1),
}));
}}
>
Update State.B
</button>
</div>
);
};
const DisplayA = () => {
const a = complexState.useValueSelector(value => value.a);
return <div>State.A: {a}</div>;
};
const DisplayB = () => {
const b = complexState.useValueSelector(value => value.b);
return <div>State.B: {b}</div>;
};
const Container = () => {
return (
<div>
<h2>Container</h2>
<Controls />
<DisplayA />
<DisplayB />
</div>
);
};
const Page = () => {
return (
<main>
<Container />
</main>
);
};
export default Page;
In this example, when the user clicks on the buttons of the Controls
component, the state changes and each component that is subscribed to a part of the container's state via useValueSelector
hook will be notified and re-rendered as a result. In other words, DisplayA
component re-renders only when value.a
changes (same thing for the DisplayB
component and the value of value.b
state.).
You can pass in any selector you want. Think of selectors as a state transformer where you can transform a complex state into another simpler shape.
For example:
const { a, b } = complexState.useValueSelector(value => ({ a: value.a, b: value.b}));
const valueOfB = complexState.useValueSelector(value => value.b);
// Or subscribe to a new computed value
const computedValue = complexState.useValueSelector(value => value.a * 2 + Number(value.b));
Cool, huh?
So, what about using the complex state in a non-React environment?
You can opt-in selectedSubscribe
instead of subscribe
.
For example:
// Logs the new value of `value.a` on selected state changes
complexState.selectedSubscribe(value => value.a, console.log);
More control of re-rendering and emission changes?
For more control over re-rendering (in React environment) and emission changes (in non-React environment) try to pass in isEqual
parameter to the useValueSelector
and selectedSubscribe
(check the API section for more information).
By default, we are using
Object.is
as equality check function.
API
createStateContainer
:
declare const createStateContainer: <T>(initializer: T | (() => T)) => {
/**
* Subscribes to the changes of the container's state value
* and returns the unsubscribe function.
*/
subscribe(subscribeCallback: (value: T) => void): () => void;
/**
* Subscribes to the changes of the container's selected state values
* and returns the unsubscribe function.
*
* For more control over emission changes, you may provide a custom equality function.
*/
selectedSubscribe<P>(
selector: (value: T) => P,
subscribeCallback: (value: P) => void,
/**
* A custom equality function to control emission changes.
*/
isEqual?: (a: P, b: P) => boolean,
): () => void;
/**
* Gets the value of the state.
*
* Please note that this function is not reactive!
* Avoid using this in the React's rendering phase.
*/
getValue(): T;
/**
* Updates the value of the state and notifies the subscribers.
*/
updateValue(newValue: T): void;
/**
* A React hook to read the value of the state.
*
* This is a reactive function and updates on state value change.
*/
useValue(): T;
/**
* A React hook to read the values of the selected states.
*
* This is a reactive function and updates on selected state values change.
*
* For more control over re-rendering, you may provide a custom equality function.
*/
useValueSelector(
selector: (value: T) => P,
/**
* A custom equality function to control re-rendering.
*/
isEqual?: (a: P, b: P) => boolean,
): P;
/**
* A React hook to update the value of the state and notify the subscribers.
*/
useUpdateValue(): React.Dispatch<React.SetStateAction<T>>;
};
A function that creates a container for the state to live in. This function needs the initial value of the state you are containerizing. You can provide a initializer
parameter (which is either an initial value or a function returns the initial value) to initialize the state value.
Contributing
Read the contributing guide to learn about our development process, how to propose bug fixes and improvements, and how to build and test your changes.
Contributing to "React Containerized State" is about more than just issues and pull requests! There are many other ways to support the project beyond contributing to the code base.
License
This project is licensed under the terms of the MIT license.