Socket
Socket
Sign inDemoInstall

ai0

Package Overview
Dependencies
4
Maintainers
1
Versions
1
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    ai0

Application state


Version published
Maintainers
1
Created

Readme

Source

🖤 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();

Keywords

FAQs

Last updated on 26 May 2023

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc