Shared states for React.js
react-stores
Shared states for React.js (a flux-way shared stores without actions and dispatchers).
How to install
yarn add react-stores
or npm i react-stores --save
Demo
- Clone this repo
cd
into ityarn install
ornpm i
npm run demo
localhost:9000
in your browser
Tests
npm run test
How to use
First you need to create a Store
singleton
// store.ts
import {Store} from "react-stores";
export namespace CommonStore {
// State interface
export interface State {
counter: number
}
// Store's state initial values
const initialState: State = {
counter: 0
};
export let store: Store<State> = new Store<State>(initialState);
}
Then you need to create a StoreComponent
that will use store singleton
// component.tsx
import * as React from "react";
import {StoreComponent, Store} from "react-stores";
import {CommonStore} from "./store";
interface Props {
name: string
}
interface State {
counter: number
}
interface StoresState {
common: Store<CommonStore.State>
}
export class App extends StoreComponent<Props, State, StoresState> {
constructor() {
super({
common: CommonStore.store
});
}
private increaseCommon():void {
// You can mutate stores as local component state values
this.stores.common.setState({
counter: this.stores.common.state.counter + 1
});
}
private increaseLocal():void {
// Also you can use local state as natural React.Component
this.setState({
counter: this.state.counter + 1
});
}
render() {
return (
<div>
<p>Component name: {this.props.name}</p>
<p>Common counter value: {this.stores.common.state.counter.toString()}</p>
<p>Local counter value: {this.state.counter.toString()}</p>
<button onClick={this.increaseCommon.bind(this)}>Increase common counter value</button>
<button onClick={this.increaseLocal.bind(this)}>Increase local counter value</button>
</div>
);
}
}
v1.2.0 Event driven component–store connection
import * as React from "react";
import {StoreComponent, Store, StoreEventType, StoreEvent} from "react-stores";
import {CommonStore} from "./store";
interface Props {
}
interface State {
commonStoreState: CommonStore.State
}
export class App extends React.Component<Props, State> {
private storeEvent: StoreEvent<CommonStore.State> = null;
state: State = {
commonStoreState: null
}
comonentDidMount() {
// Add store state event binder
this.storeEvent = CommonStore.store.on(StoreEventType.storeUpdated, (storeState: StoreState, prevState: StoreState) => {
this.setState({
commonStoreState: storeState
});
});
}
componentDidUnmount() {
// Remove store state event binder
this.storeEvent.remove();
}
render() {
return (
<div>
<p>Common counter value: {this.state.commonStoreState.counter.toString()}</p>
</div>
);
}
}
v1.3.0 Decorator for make React Component React Sores driven. You can use multiple
@followStore(CommonStore.store, ['counter']) // follow for only "counter" store state
@followStore(SomeOtherStore.store) // follow for all store's states
export class CounterDecorator extends React.Component<Props, State> {
public render() {
return (
<div>
<p>
Shared state counter: {CommonStore.store.state.counter.toString()}
</p>
</div>
);
}
}
Now you can use it as usual
import {App} from "./component";
ReactDOM.render(
React.createElement(App),
document.getElementById('app')
);
You can mutate states from any point of your app like this
import {CommonStore} from "./store";
CommonStore.store.setState({
counter: 100500
});
...or even like a flux actions
import {CommonStore} from "./store";
export class CommonActions {
static increaseCounter(state:CommonStore.State):void {
CommonStore.store.setState(state);
}
}
// Note that you always have your store interface, you haven't lost typization consistency
// of your app like it always occurs in Flux/Redux apps in action -> store communication
let newState: CommonStore.State = {
counter: 100500
}
CommonActions.increaseCounter(newState);
Also you can get store state values from everywhere in your app
import {CommonStore} from "./store";
console.log(CommonStore.store.state.counter);
API
StoreComponent lyfecycle
storeComponentDidMount(): void
storeComponentWillUnmount(): void
storeComponentWillReceiveProps(nextProps:Props): void
storeComponentWillUpdate(nextProps:Props, nextState:State): void
storeComponentDidUpdate(prevProps:Props, prevState:State): void
shouldStoreComponentUpdate(nextProps:Props, nextState:State): boolean
storeComponentStoreWillUpdate(): void
storeComponentStoreDidUpdate(): void
Store
constructor<StoreState>(initialState<StoreState>, options: StoreOptions);
interface StoreOptions {
/*
Populated from Freezer.
With live mode on, freezer emits the update events just when the changes happen, instead of batching all the changes and emiting the event on the next tick. This is useful if you want freezer to store input field values.
*/
live?: boolean; // (default: false)
/*
Populated from Freezer.
It's possible to store class instances in freezer. They are handled like strings or numbers, added to the state like non-frozen leaves. Keep in mind that if their internal state changes, freezer won't emit any update event. If you want freezer to handle them as freezer nodes, set 'freezerInstances: true'. They will be frozen and you will be able to update their attributes using freezer methods, but remember that any instance method that update its internal state may fail (the instance is frozen) and wouldn't emit any update event.
*/
freezeInstances?: boolean; // (default: false)
/*
Populated from Freezer.
Once you get used to freezer, you can see that immutability is not necessary if you learn that you shouldn't update the data directly. In that case, disable immutability in the case that you need a small performance boost.
*/
mutable?: boolean; // (default: false)
}
setState(newState: StoreState): void // Set store's state to provided new one
resetState(): void // Reset srote to it's initialState
resetPersistence(): void // Reset persistent data
update(): void // Force update all binded components
on(eventType: StoreEventType | StoreEventType[], callback: (storeState: StoreState, prevState: StoreState, type: StoreEventType) => void): StoreEvent<StoreState> // State event binder
StoreEvent
remove(): void
StoreEventType
'all' // fires with every other events (init or update)
'init' // fires once at as soon as event has bound
'update' // fires at each store update
'dumpUpdated' // fires at each dump update
Persistence
// LocalStorage
export let store: Store<State> = new Store<State>(initialState, new StorePersistentLocalSrorageDriver('comon'));
Persistent driver
abstract class StorePersistantDriver<StoreState> {
constructor(readonly name: string) {}
public abstract write(state: StoreState): void;
public abstract read(): StoreState;
}