react-model-store
TypeScript icon, indicating that this package has built-in type declarations

0.4.0 • Public • Published

React Model Store

npm version Build Status Coverage Status License: MIT

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')
);

Package Sidebar

Install

npm i react-model-store

Weekly Downloads

0

Version

0.4.0

License

MIT

Unpacked Size

149 kB

Total Files

12

Last publish

Collaborators

  • bucatinicode