README
domein
Just another juicy state manager lib for your js apps.
Heavily inspired by redux obviously.
๐ Features
Why domein over redux or mobx or whatever
- Always async, no more thunks or whatever
- Domain based
- Dead simple API
- No more dispatch and other stuff, just plain functions and objects
- Middlewares
- This lib really loves TypeScript
- You can control actions flow using only async/await and Promise.all
โ Install
# npm
npm i domein
# yarn
yarn add domein
๐ Usage
Basic state management
// ./state/index.ts
import { create } from 'domein';
import mydomain from './mydomain';
export const state = create({
mydomain,
});
// ./state/mydomain/index.ts
import { domain } from 'domein';
interface IState {
value: number;
}
function initialstate(): IState {
return {
value: 0,
};
}
function actions() {
return {
decrement,
increment,
set,
setAsync,
}
}
function decrement(state: IState) {
return {
value: state.value - 1,
}
}
function increment(state: IState) {
return {
value: state.value + 1,
}
}
function set(state: IState, value: number) {
return { value };
}
async function setAsync(state: IState, value: number) {
// becase reasons
return new Promise(resolve =>
setTimeout(
() => resolve({ value }),
100
)
);
}
export default domain(initialstate, actions);;
// ./app.ts
import { state } from './state';
const { get, actions } = state;
await actions.mydomain.set(10)
await actions.mydomain.decrement()
await actions.mydomain.decrement()
await actions.mydomain.increment()
get() // { mydomain: { value: 9 } }
Composing multiple actions
Since an action is a function, you can compose them as you want.
importย { domain } from 'domein';
import * as api from './api';
interface IState {
content: string;
title: string;
wordcount: number;
}
function actions() {
return {
load,
setWordCount,
}
}
function initialstate() {
return {
content: '',
title: '',
wordcount: 0,
};
}
const myblogpost = domain(initialstate, actions);
async function load(state: IState) {
const newstate = await api.load();
return setWordCount({... state, ...newstate});
}
function setWordCount(state: IState) {
return {
... state,
wordcount: state.content.match(/\S+/g),
}
}
Actions flow
You could sometimes have a particular scenario where you want some actions to be run in parallels or in waterfall.
To control actions flow, you have to simply create a new function which will await single actions
very important for parallel actions
Parallel actions could lead to wrong state merging. To prevent this behaviour, you will have to merge domain's state by excluding properties which will be updated in parallel
example
// state/index.ts
import { create, domain } from 'domein';
interface IState {
userscount: number;
rolescount: number;
loading: boolean;
users: IUser[];
roles: IRole[];
}
function initialstate(): IState {
return {
loading: false,
roles: [],
rolescount: 0,
users: [],
userscount: 0,
};
}
function actions() {
return {
loadroles,
loadusers,
setloading,
}
}
async function loadusers(state: IState) {
const users = await api.loadusers();
const userscount = users.length;
// done this in order to avoid overriding of roles and rolescount
const { loading } = state;
return {
loading,
users,
userscount,
};
}
async function loadroles(state: IState) {
const roles = await api.loadroles();
const rolescount = roles.length;
// done this in order to avoid overriding of users and userscount
const { loading } = state;
return {
loading,
roles,
rolescount,
};
}
function setloading(state: IState, loading: boolean) {
return {
... state,
loading,
};
}
const mydomain = domain(initialstate, actions);
export default create({ mydomain });
// ./otherfile.ts
import state from './state';
export async function loadseries() {
const { mydomain } = state.actions;
await mydomain.setloading(true);
await mydomain.loadusers();
await mydomain.loadroles();
await mydomain.setloading(false);
}
export async function loadparallel() {
const { mydomain } = state.actions;
await mydomain.setloading(true);
Promise.all([ mydomain.loadusers(), mydomain.loadroles() ]);
await mydomain.setloading(false);
}
Creating middlewares
import { build, MiddlewareFn } from 'domein';
import mydomain from './mydomain';
function logger(): MiddlewareFn {
return message => {
const {
action,
domain,
state,
} = message;
console.log(
`[${domain}][${action}]`,
'previous', state.prev,
'next', state.next,
);
return nextstate;
}
}
const state = build({ mydomain }, logger);
state.actions.mydomain.set(10) // logs: [update][mydomain][set] previous { value: 0 } next { value: 10 }
๐ Docs
create
The create function simply creates a new state.
It accepts one parameter which is an object of key/value pairs, where keys are domain names and values are domains
A created state, returns an object with one property and two functions:
- actions, which are the subscribed actions, organized by domain
- get, returns current state
- subscribe, subscribes a function to state changes. Every state change will trigger it, telling the subscribed function which domain did update and giving it the new state
const state = create({ mydomain });
state.subscribe(message => console.log(message.domain, message.state));
state.actions.mydomain.set(10) // logs: 'mydomain', { value: 10 }
domain
To create a domain use it's related function.
This function accepts two functions, the first which returns the initial domain state and the second which returns an object containing actions
import { domain } from 'domein';
// domain's state interface
interface IDomainState { }
function initialstate(): IDomainState {
return { }
}
function actions() {
return { }
}
export default domain(initialstate, actions);
๏ธโค๏ธ Contributing
Every contribution is really welcome!
If you feel that something can be improved or should be fixed, feel free to open an issue with the feature or the bug found.
If you want to fork and open a pull request (adding features or fixes), feel free to do it. Remember only to use the master
branch as a base.
If you plan adding a new feature, please prefix the branch with the feat/branchname
.
If you plan fixing something, please prefix the branch with the fix/branchname
.
If you plan refactoring something, please prefix the branch with the refactor/branchname
.
If you adding something which does not involve the runtime behavious, please prefix the branch with the chore/branchname
.
Read the contributing guidelines
๐ Licence
Read the licence