minsto
v1.2.6
Published
A mini store for javascript/React app
Downloads
17
Maintainers
Readme
Minsto
A mini store for javascript/React app
Features
- Zero configuration
- No boilerplate
- Extensive TypeScript support
- Global, shared, or component level stores
- Plugins supported
- React Suspense supported
- Future action listening supported
- Cancellable action dispatching supported
- React hooks based API
- Computed properties (support async mode) - i.e. derived data
- Data fetching / side effects
- Local store supported
- React Native supported
- Hot Reloading supported
- Reactotron supported
Get Started
Installation
npm install minsto --save
Step 1 - Create your store
import minsto from "minsto";
const todoStore = minsto({
state: {
items: ["Create store", "Use the store"],
},
actions: {
add(store, payload /* item title */) {
// state.items is immutable
// we must use array.concat to create new copy of items and append new item at end
store.items = store.items.concat(payload);
},
},
});
Step 2 - Use the store
import React, { useRef } from "react";
import todoStore from "./todoStore";
import useStore from "minsto/react";
function TodoList() {
const inputRef = useRef();
const { todos, add } = useStore(todoStore, (store) => {
return {
todos: store.items,
add: store.add,
};
});
return (
<div>
<input ref={inputRef} />
<button onClick={() => add(inputRef.current.value)}>Add</button>
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo}</li>
))}
</ul>
</div>
);
}
The useStore hook has the following signature.
const result = useStore(CounterStore, (store) => {
return {
count: store.count,
increase: store.increase,
};
});
console.log(result.count, result.increase);
The hook accepts a storeMapper function. The storeMapper function will be provided your input store and should return the slice of state or actions required by your component. Anytime an update occurs on your store the storeMapper function will be executed, and if the newly mapped result does not equal the previously mapped result your component will be rendered with the new value.
Using actions to update state
In this section we will tell you how to update store state
Defining actions on our model
We are going to define two actions on our counterStoreModel; one to increase count state, and another to decrease count state. Action is pure function that retrieves two arguments (store object and action payload)
const counterStoreModel = {
state: {
count: 0,
},
// all action definitions must be placed inside actions block
actions: {
increase(store, payload) {
store.count++;
},
decrease(store, payload) {
store.count--;
},
},
};
Because action is pure function, you can write unit test easily
test("increase", () => {
const state = { count: 0 };
counterStoreModel.actions.increase(state);
expec(state.count).toBe(1);
});
Using component store
Counter App with custom store hook
Computed Properties
Computed properties are the perfect candidate to help us clean up the more advanced state mapping that is happening within some of our application's components. Let's refactor each derived data case.
First up, let's add a computed property to represent the total todo items.
const todoModel = {
state: {
todos: [
{ id: 1, title: "todo 1", completed: false },
{ id: 2, title: "todo 2", completed: true },
],
},
computed: {
total: (state) => state.todos.length,
},
};
Next up, we will add a computed property to represent the completed todos and active todos.
const todoModel = {
state: {
todos: [
{ id: 1, title: "todo 1", completed: false },
{ id: 2, title: "todo 2", completed: true },
],
},
computed: {
total: (state) => state.todos.length,
completed: (state) => state.todos.filter((todo) => todo.completed).length,
active: (state) => state.todos.filter((todo) => !todo.completed).length,
},
};
Computed properties optionally allow you to provide an array of state resolver functions as the first argument to the computed property definition. These state resolver functions will receive the state that is local to the computed property, as well as the entire store state, and allow you to resolve specific slices of state that your computed function will take as an input.
const todoModel = {
state: {
todos: [
{ id: 1, title: "todo 1", completed: false },
{ id: 2, title: "todo 2", completed: true },
],
},
computed: {
total: (state) => state.todos.length,
completed: (state) => state.todos.filter((todo) => todo.completed).length,
active: (state) => state.todos.filter((todo) => !todo.completed).length,
// show todo list stats
stats: [
// named computed properties / state resolvers
"total",
"completed",
"active",
(total, completed, active) => ({ total, completed, active }),
],
},
};
Local Store
If you don't want to mess many things into the global state, you can use local store to split app logics that used to only specified component. The local store instance stored in host component. It will be removed when its host component is unmounted. A host component can contain many local store, local store model can be reused by many components.
import { useLocalStore } from "minsto/react";
const CounterModel = {
state: {
count: 0,
step: 1,
},
actions: {
increase(store) {
store.count += store.step;
},
},
};
const Counter = ({ step = 1 }) => {
const store = useLocalStore(CounterModel);
// pass step prop to store
store.step = step;
return <h1 onClick={store.increase}>{store.count}</h1>;
};
Working with Development Tools
Reactotron
Install reactotron-redux
npm install --save-dev reactotron-redux
Configuring
import Reactotron from "reactotron";
import { reactotronRedux } from "reactotron-redux";
import minsto from "minsto";
import { connectReactotronRedux } from "minsto/reactotron";
const reactotron = Reactotron.configure({ name: "React Native Demo" })
.use(reactotronRedux())
.connect();
const storeEnhander = reactotron.createEnhancer();
const counterStore = minsto({
state: { count: 0 },
actions: {
increase: (store) => store.count++,
},
});
connectReactotronRedux(storeEnhander, counterStore);