🖤 act0 - not a "redux"
Type-safe React application state library with zero setup. Powered by Object.defineProperty
.
Quick start
npm i act0
import Act0 from 'act0';
class RootStore extends Act0 {
count: 1,
}
const store = new RootStore();
export default () => {
const count = store.use('count');
return (
<div onClick={() => store.count++}>Clicks: {count}</div>
);
}
Slow start
Create your store with ES6 classes extended by Act0
. It's recommended to split it into multiple objects that I call "sub-stores". In the example below Users
and Companies
are sub-stores. Level of nesting is unlimited as for any other JavaScript object.
import Act0 from 'act0';
class Users extends Act0 {
ids = [1, 2, 3];
readonly loadUsers = () => fetch('/users')
}
class Companies extends Act0 {
name = 'My Company';
}
class RootStore extends Act0 {
readonly users = new Users();
readonly companies = new Companies();
readonly increment = () => this.count++;
readonly decrement = () => this.count--;
count = 0;
}
const store = new RootStore();
export default store;
Use readonly
prefix to protect class members to be reassigned.
Use use
method to access store
object properties in your component.
const MyComponent = () => {
const count = store.use('count');
const ids = store.users.use('ids');
const name = store.companies.use('ids');
To change value, assign a new value.
store.count++;
store.users.ids = [...store.users.ids, 4]
Call methods for actions.
useEffect(() => {
store.users.loadUsers().then(() => {
store.decrement();
});
}, []);
Pass values returned from use
as dependencies for hooks.
const count = store.use('count');
const callback = useCallback(() => { console.log(count); }, [count])
You can split sub-stores into multiple files and access root store using first argument.
import Users from './Users';
import Companies from './Companies';
export class RootStore {
readonly users: Users;
readonly companies: Companies;
constructor() {
this.users = new Users(this);
this.companies = new Companies(this);
}
}
import type { RootStore } from '.';
export default class Users {
#store: RootStore;
constructor(store: RootStore) {
this.#store = store;
}
readonly loadUsers() {
const something = this.#store.companies.doSomething();
}
}
I recommend to destructure all methods that are going to be called to make it obvious and to write less code at hooks and components.
const MyComponent = ({ id }) => {
const { increment, decrement, users: { loadUsers } } = store;
}
or better
const { increment, decrement, users: { loadUsers } } = store;
const MyComponent = ({ id }) => {
}
Act0.of
If you don't want to define class you can use this static method. Act0.of<T>(data?: T): Act0 & T
returns Act0
instance with use
method and uses firtst argument as initial values.
class RootStore extends Act0 {
readonly coordinates = Act0.of({ x: 0, y: 100 });
const MyComponent = () => {
const x = store.coordinates.use('x');
const y = store.coordinates.use('y');
You can also define custom record:
class RootStore extends Act0 {
data: Act0.of<Record<string, Item>>();
}
And acces values as usual:
const MyComponent = ({ id }) => {
const item = store.data.use(id);
For a very small app you can define your entire application state using Act0.of
method (also exported as a constant).
import { of as act } from 'act0';
const store = act({
count: 1,
companies: act({
name: 'My company',
someMethod() { }
}),
});
export default store;
import store from './store';
const MyComponent = () => {
const count = store.use('count');
const name = store.companies.use('name');