use42 v0.0.7
🖤 act0 - not a "redux"
Type-safe React application state library with zero setup. Powered by
Object.defineProperty
.
Quick start
npm i act0
# yarn add act0
import Act0 from 'act0';
// 1. Define your root store
// Act0 adds "use" method to the RootStore instance, that's all what it does
class RootStore extends Act0 {
count: 1,
}
const store = new RootStore();
// 2. Use
export default () => {
const count = store.use('count'); // same as store['count'] but reactive
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.
// store.ts
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();
// ...
});
}, []); // no dependencies for methods
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.
// ./store/index.ts
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);
}
}
// ./store/Users.ts (Companies.ts is similar)
import type { RootStore } from '.'; // "import type" avoids circular errors with ESLint
export default class Users {
#store: RootStore;
constructor(store: RootStore) {
this.#store = store;
}
readonly loadUsers() {
// you have access to any part of the store
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');
// ..
// store.coordinates.x = 100;
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); // same as store.data[id] but reactive
// ...
// store.data[id] = someValue; // triggers the component to re-render
For a very small app you can define your entire application state using Act0.of
method (also exported as a constant).
// store.ts
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'); // same as store['count'] but reactive
const name = store.companies.use('name'); // same as store.companies['name'] but reactive
// store.companies.someMethod();
12 months ago