storeact
v3.0.0
Published
Zero-configuration store for React. One API rule them all
Downloads
18
Maintainers
Readme
Storeact
Zero-configuration store for React. One API rule them all.
Installation
npm i storeact --save
Get started
ES2015+ and Typescript:
import storeact from "storeact";
Example
Let's make an increment/decrement simple application with React:
First, create your store. This is where your application state will live:
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const CounterStore = () => {
let count = 0;
return {
state() {
return count;
},
increase() {
count++;
},
decrease() {
count--;
},
async increaseAsync() {
await delay(1000);
this.increase();
},
};
};
The store is just pure function, we define some APIs and export count value via state() method
Now create your component. With Storeact your component can focus 100% on the UI and just call the actions that will automatically update the state:
const store = storeact(CounterStore);
function App() {
const count = store.select();
const { increase, decrease, increaseAsync } = store;
return (
<div className="App">
<h1>{count}</h1>
<div>
<button onClick={() => increase()}>Increase</button>
<button onClick={() => decrease()}>Decrease</button>
<button onClick={() => increaseAsync()}>Increase Async</button>
</div>
</div>
);
}
Storeact 's cool features
- Simple setup
- Simple API (only one)
- Store definition is pure function
- Less boilerplate
- Readability
- Configurable
- Easy to test
- Asynchronous action
- Future actions awaiting
- High performance
- Compatible with Immutable JS
Advanced Usages
Using action context
When an action is dispatching, storeact creates a task for each action call then pass that task as second argument of action. Using action task to control execution flow more easily.
Delay execution using task.delay(ms)
const Store = () => {
let count = 0;
return {
increase() {
count++;
},
async increaseAsync(payload, task) {
await task.delay(1000);
this.increase();
},
};
};
Using task.cancelOn(...cancelActions)
const Store = () => {
return {
cancel() {},
async search(payload, task) {
task.cancelOn(this.cancel);
await task.delay(3000);
if (task.cancelled()) return;
// update state logic here
},
};
};
Using task.debounce(ms)
You can use debounce to wait certain amount of time before next execution block
const Store = () => {
return {
cancel() {},
async search(payload, task) {
await task.debounce(500);
// update state logic here
},
};
};
Wait for future action dispatching
const Store = () => {
return {
async startDataFetching() {
const data = await fetch("api");
this.dataFetched(data);
},
dataFetched() {},
async search(payload, task) {
this.startDataFetching();
// wait until dataFetched action dispatched
const data = await task.when(this.dataFetched);
// do something
},
};
};
You can improve above example with cancellable searching logic
const Store = () => {
return {
async startDataFetching() {
const data = await fetch("api");
this.dataFetched(data);
},
dataFetched() {},
cancel() {},
async search(term, task) {
// search progress will be cancelled if cancel action dispatched
task.cancelOn(this.cancel);
await task.debounce(500);
this.startDataFetching(term);
// wait until dataFetched action dispatched
const data = await task.when(this.dataFetched);
// do something
},
};
};
Handling async data loading
You can use AsyncValue to handle async data loading easily
const TodoStore = ({ async }) => {
// create async value object with empty array as default value
const list = async([]);
return {
init(task) {
// start data loading
task.mutate(list, fetch("todo-api"));
},
state() {
// return todos state is promise
return { todos: list.promise };
},
};
};
In React component, to retrieve promised value we use selector util
const store = storeact(TodoStore);
const TodoCount = () => {
const count = store.select((state, util) => {
return util.value(state.todos).length;
});
return <h1>Todos ({count})</h1>;
};
const App = () => {
return (
<React.Suspense fallback="Loading...">
<TodoCount />
</React.Suspense>
);
};
A "Loading..." message will show if todos promise still not fulfilled
util.loadable()
Using util.loadable() to retrieve Loadable object to render loading progress manually
const store = storeact(TodoStore);
const TodoCount = () => {
const loadable = store.select((state, util) => {
return util.loadable(state.todos);
});
if (loadable.state === "loading") return <div>Loading...</div>;
if (loadable.state === "hasError")
return <div>Oops, something went wrong. {loadable.error.message}</div>;
// loadable.state === 'hasValue'
return <h1>Todos ({count})</h1>;
};