React Model Store
The simple state management library for React.
This library provides model-based state management with Hooks and Context API of React.
Install
npm install react-model-store
or
yarn add react-model-store
Requirements
- React 16.8.0 or newer
Examples for Typescript
Counter Example (single component pattern)
import React from 'react';import ReactDOM from 'react-dom';import { Model, useModel } from 'react-model-store'; class CounterModel extends Model { count: number = this.state(0); // Synchronous increment = () => this.count++; // Asynchronous decrement = () => setTimeout(() => this.count--, 1000);} const Counter = () => { const { count, increment, decrement } = useModel(CounterModel); return ( <div> <p>Count: {count}</p> <div> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </div> </div> );}; ReactDOM.render(<Counter />, document.getElementById('root'));
Todo Example (model provider pattern)
import React from 'react';import ReactDOM from 'react-dom';import { Model, createStore, useModel } from 'react-model-store'; interface Todo { key: number; text: string;} class ControlModel extends Model { textInput = this.ref<HTMLInputElement>(); onAddClick = this.event(); onKeyPress = this.event<React.KeyboardEvent<HTMLInputElement>>(); get text(): string { return this.textInput.current!.value; } refresh(): void { this.textInput.current!.value = ''; this.textInput.current!.focus(); }} class LogicModel extends Model { private control: ControlModel; lastKey: number = this.state(0); todos: Todo[] = this.state([]); constructor(control: ControlModel) { super(); this.control = control; this.addListener(this.control.onAddClick, () => { this.add(); }); this.addListener( this.control.onKeyPress, (e: React.KeyboardEvent<HTMLInputElement>) => { if (e.key === 'Enter') { this.add(); } } ); } add(): void { if (this.control.text) { this.todos.push({ key: ++this.lastKey, text: this.control.text, }); this.control.refresh(); } } remove(key: number): void { this.todos = this.todos.filter(todo => todo.key !== key); }} class RootModel { control = new ControlModel(); logic = new LogicModel(this.control);} class TodoModel extends Model { todo: Todo; onRemoveClick: () => void; constructor(todo: Todo) { super(); this.todo = todo; const { logic } = this.model(RootModelStore); this.onRemoveClick = logic.remove.bind(logic, todo.key); }} const RootModelStore = createStore(RootModel); const ControlPanel = () => { const { control: { textInput, onAddClick, onKeyPress }, } = useModel(RootModelStore); return ( <div> <input type='text' ref={textInput} onKeyPress={onKeyPress} /> <button onClick={onAddClick}>Add</button> </div> );}; const TodoItem = (props: { todo: Todo }) => { const { todo: { text }, onRemoveClick, } = useModel(TodoModel, props.todo); return ( <li> <button onClick={onRemoveClick}>Remove</button> <span>{text}</span> </li> );}; ReactDOM.render( <RootModelStore.Provider> <div> <ControlPanel /> <ul> <RootModelStore.Consumer> {({ logic: { todos } }) => todos.map(todo => ( <li> <TodoItem key={todo.key} todo={todo} /> </li> )) } </RootModelStore.Consumer> </ul> </div> </RootModelStore.Provider>, document.getElementById('root'));
Timer Example (high frequency re-render pattern)
import React from 'react';import ReactDOM from 'react-dom';import { Model, createStore, useModel } from 'react-model-store'; class RootModel extends Model { // RootModelStore.Provider component is re-rendered when this state is changed. running = this.state(false); resetButton = this.ref<HTMLButtonElement>(); onReset = this.event(); onToggle = this.event(() => { this.running = !this.running; this.resetButton.current!.disabled = this.running; }); get toggleText(): string { return this.running ? 'Stop' : 'Start'; }} const RootModelStore = createStore(RootModel); class HighFrequencyTimerModel extends Model { root = this.model(RootModelStore); // use RootModel // HighFrequencyTimer component is re-rendered when this state is changed. time = this.state(0); started: number = 0; stored: number = 0; constructor() { super(); this.addListener(this.root.onToggle, this.toggle); this.addListener(this.root.onReset, this.reset); } update(): void { this.time = this.stored + new Date().getTime() - this.started; } run = () => { if (this.root.running) { this.update(); setTimeout(this.run, 50); } }; toggle = () => { if (this.root.running) { this.started = new Date().getTime(); this.run(); } else { this.update(); this.stored = this.time; } }; reset = () => { this.stored = 0; this.time = 0; };} const HighFrequencyTimer = () => { const { time } = useModel(HighFrequencyTimerModel); return <span>{(time / 1000).toFixed(2)}</span>;}; const Controller = () => { const { onReset, onToggle, toggleText, resetButton } = useModel( RootModelStore ); return ( <div> <button onClick={onToggle}>{toggleText}</button> <button onClick={onReset} ref={resetButton}> Reset </button> </div> );}; ReactDOM.render( <RootModelStore.Provider> <div> <div> {/* * HighFrequencyTimer component is re-rendered frequently. * But that re-rendering doesn't cause re-rendering of the provider. */} <HighFrequencyTimer /> </div> <Controller /> </div> </RootModelStore.Provider>, document.getElementById('root'));