npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

atomx-state

v0.1.0

Published

Predictable and scalable atomic state management

Downloads

9

Readme

⚛ AtomX (Alpha) - Predictable and Scalable Atomic State

Currently in Alpha stage of development. We are looking for feedback and pull requests to make this better state management for more people.

AtomX is a scalable atomic state based state management framework designed for React, but can work with any framework with a render function.

Core principles:

  • Scalable - API simple enough for prototyping, explicit enough for large scale apps.
  • Familiar - Built to allow syntax that is similar to other workflows and frameworks.
  • Predictable - All changes to state are explicitly subscribed to, and directly changed.
  • Flexible - Atomic state can be built into many different programming patterns and workflows.
  • Batteries Included - Fully supports TypeScript and JavaScript, and includes many components and APIs to make managing atomic state easier in large scale apps.

Core components:

  • State - A single atomic value that has a getter, setter, and is an event dispatcher.
  • Store - A container for multiple atomic states. It has a getter, setter, and is an event dispatcher.
  • Collection - A list-like structure containing atomic states, which also has a getter, setter, and is an event dispatcher.
  • Subscribing - All AtomX structures can be subscribed to, and have event listeners.
  • Computed State - State that's value is derived from changes to other state.

Installing

Platform independent library:

npm install atomx-state

React based library:

npm install atomx-state-react

Examples

Using Classes

Component State:

import { state, Subscriber } from "atomx-state";

// extend our component to support subscribing to atomic state
class CountExample extends Subscriber(React.Component) { 
  // create initial atomic state
  name = state('My Counter');
  count = state(0); 
  
  increment = () => this.count.set(this.count.get() + 1); // action to add 1 to state

  render() {
    this.subscribe(this.count, this.name); // subscribe to the state changes
    // get and save the current value into variables
    let count = this.count.get(); 
    let name = this.name.get();
    return (
        <p>
          {name} Clicked: {count} times.
          <button onClick={this.increment}>+</button>
          <input 
            type="text" 
            value={ name.get() } 
            onChange={ (e) => name.set(e.target.value) }
          >
        </p>
    );
  }
}
import { state, Subscriber } from "atomx-state";

// extend our component to support subscribing to atomic state
class CountExample extends Subscriber(React.Component) { 
  // create initial atomic state
  name = state<string>('My Counter');
  count = state<number>(0); 
  
  increment = () => this.count.set(this.count.get() + 1); // action to add 1 to state

  render() {
    this.subscribe(this.count, this.name); // subscribe to the state changes
    // get and save the current value into variables
    let count: number = this.count.get(); 
    let name: string = this.name.get();
    return (
        <p>
          {name} Clicked: {count} times.
          <button onClick={this.increment}>+</button>
          <input 
            type="text" 
            value={ name.get() } 
            onChange={ (e) => name.set(e.target.value) }
          >
        </p>
    );
  }
}

Global State:

import { Store, state } from "atomx-state";

class CounterStore extends Store {
  count = state(0);
  name = state('Unnamed Counter');
  
  constructor() {
    super();
    this.init(); // initialize this store
  }

  increment = () => {
    this.count.set(this.count.get() + 1);
  }

  setName = (name) => {
    this.name.set(name);
  }
}

export default new CounterStore();
import { Store, state } from "atomx-state";

class CounterStore extends Store {
  count = state<number>(0);
  name = state<string>('Unnamed Counter');
  
  constructor() {
    super();
    this.init(); // initialize this store
  }

  increment = () => {
    this.count.set(this.count.get() + 1);
  }

  setName = (name) => {
    this.name.set(name);
  }
}

Using Functional Components

Component State:

import { state } from "atomx-state";
import { subscribe } from "atomx-state-react";
// create initial atomic state.
let count = state(0); 
let name = state('My Counter');

function CountExample {
  
  let increment = () => count.set(count.get() + 1); // action to add 1 to state
  subscribe(count, name); // subscribe to the state changes

  render() {
    return (
        <p>
          {name.get()} Clicked: {count.get()} times.
          <button onClick={this.increment}>+</button>
          <input 
            type="text" 
            value={ name.get() } 
            onChange={ (e) => name.set(e.target.value) }
          >
        </p>
    );
  }
}
import { state, subscribe } from "atomx-state";
// create initial atomic state.
let count = state<number>(0);
let name = state<string>('My Counter');

function CountExample {
  
  
  let increment = () => count.set(count.get() + 1); // action to add 1 to state.
  subscribe(count, name); // subscribe to the state changes

  render() {
    return (
        <p>
          {name.get()} Clicked: {count.get()} times.
          <button onClick={this.increment}>+</button>
          <input 
            type="text" 
            value={ name.get() } 
            onChange={ (e) => name.set(e.target.value) }
          >
        </p>
    );
  }
}

Future features...

  • Time travel
  • Debugger
  • Rehydration

Working Online Examples

Classes (React):

Functional (React):

AtomX API

⚛ State

"State" is a piece of atomic state. It can be get, set, subscribed to, listened to (using on or off), and dispatched from. Sometimes you need your app to react to a specific event, and not just change based on what state you've subscribed to.

state(value: any)

// Creating new atomic state

// JavaScript
count = state(0);

// TypeScript
count = state<number>(0);

Create a new instance of state;

State.get()

count.get(); // 0

Returns the value of the state.

State.set(state: Object)

Store.set();

Sets the value of the state.

State.reset()

count.reset();

Resets the value in the state to the initial value, even if the original value is undefined.

State.on(eventName: string, payload: any)

count.on(StoreEvents.CHANGED, changeHandler);

function changeHandler(storeReferece){
    console.log('Store changed!')
}

Add an event listener to your state. Only event currently dispatched is StoreEvents.CHANGED when the state value is changed.

State.off(eventName: string, handlerFunction: Function)

count.off(StateEvents.CHANGED, changeHandler);

Remove an event listener from your state.

State.dispatch(eventName: string, payload: any)

count.dispatch('INCREMENT', count.get());

// Somewhere else in your app

count.on('INCREMENT', countIncrementHandler);

Dispatch an event from your state.

🔒 Store

A store is a way to contain and manage multiple atomic states and actions on those states. A store, when initialized, subscribes to all state within it. It is also an event dispatcher, and has some other convenience features. You could build your own version of this using the principles above of atomic state, but this is our "batteries included" API for storing all your state.

The examples below assume this global state matches the counter example found near the beginning of the document.

Store.get()

Store.get(); // { count: 0 };

Transforms all atomic states within the store into a flat object.

Store.set(state: Object)

Store.set({
  count: Store.count.get() + 1
});

Set any matching atomic state using and object.

Store.subscribe(changeHandler: Function)

Store.subscribe(storeChangeHandler);

function storeChangeHandler(storeReference){
  console.log('Store changed!')
}

Subscribe a handler to any changes to the store. The handler will be called when any state within the store changes.

Store.reset()

Store.reset();

Resets all state found within the store to their original values.

Store.on(eventName: string, payload: any)

Store.on(StoreEvents.CHANGED, changeHandler);

function changeHandler(storeReferece){
    console.log('Store changed!')
}

Add an event listener to your store. Only event currently dispatched is StoreEvents.CHANGED when any atomic state within the store is changed.

Store.off(eventName: string, handlerFunction: Function)

Store.off(StoreEvents.CHANGED, changeHandler);)

Remove an event listener to your store.

Store.dispatch(eventName: string, payload: any)

UserStore.dispatch('LOGIN_SUCCESSFUL', userData);

// Somewhere else in your app

UserStore.on('LOGIN_SUCCESSFUL', userLoginHandler);

Dispatch an event from your store.

Store.init()

Store.get(); // { count: 0 };

Usually called in the constructor of a new store. This initializes the store, making sure that all state in the store is subscribed to.

📚 Collection

A collection stores a list of states. State can be added, removed, retrieved, and changed.

Example:

import { Store, state } from "atomx-state";

class TodoItem extends Store {
  name = state('');
  completed = state(false);
  
  constructor(initialName) {
    super();
    if(initialName) this.name.set(initialName);
    this.init(); // initialize this store
  }

  done = () => {
    this.completed.set(true);
  }
}

export class Todo extends Store {
  todos = collection();

  constructor() {
    super();
    this.init(); // initialize this store
  }

  addTodo = (name: String) => {
    this.counters.add(new TodoItem(name));
  };
}
import { Store, state } from "atomx-state";

class TodoItem extends Store {
name = state<string>('');
completed = state<boolean>(false);

constructor(initialName: boolean) {
  super();
  if(initialName) this.name.set(initialName);
  this.init(); // initialize this store
}

done = () => {
  this.completed.set(true);
}
}

export class Todo extends Store {
todos = collection<TodoItem>();

constructor() {
  super();
  this.init(); // initialize this store
}

addTodo = (name: String) => {
  this.counters.add(new TodoItem(name));
};
}

collection(value: any)

// Creating new atomic state

// JavaScript
count = collection();

// TypeScript
count = collection<TodoStore>();

Create a new instance of a collection;

Collection.get()

collection.get(); // [ counter1, counter2, counter3, ...];

Returns all state within the collection as an array.

Collection.set(state: Object)

collection.set([counter1, counter2, counter3]);

Replaces the collection with a new array of values.

Collection.subscribe(callback: Function)

collection.subscribe(storeChangeHandler);

function collectionChangeHandler(collectionReference){
  console.log('Store changed!')
}

Subscribe a handler to the collection that will be called when an item is added, removed, or changed.

Collection.filter( filterFunction: (value: CollectionType, index: number, array: CollectionType[]) )

let completed = collection.filter( todo => todo.isCompleted.get() === true );

Returns a filtered array of results where the filter function returns true.

Collection.reset()

collection.reset();

Resets the collection to its initial state, even if that's empty.

Collection.on(eventName: string, payload: any)

collection.on(StoreEvents.ADDED, addHandler);

function addHandler(collectionReferece){
    console.log('Item added!')
}

Add an event listener to your store. Available collection events to dispatch are:

CollectionEvents.CHANGED

CollectionEvents.ADDED

CollectionEvents.REMOVED

Collection.off(eventName: string, handlerFunction: Function)

collection.off(StoreEvents.ADDED, addHandler);

Remove an event listener to your store.

Collection.dispatch(eventName: string, payload: any)

collection.dispatch('LOAD_COMPLETE', loadHandler);

// Somewhere else in your app

collection.on('LOAD_COMPLETE', loadHandler);

Dispatch an event from your collection.

📚 Computed State

The value of Computed State updates when any state within a referenced store changes.

Example:

import { Store, state } from "atomx-state";

class CounterStore extends Store {
  count = state(0);
  countPlusTen = computed(this, () => {
    return this.count.get() + 10;
  });
  
  constructor(initialValue) {
    super();
    this.init(); // initialize this store
  }

  increment = () => {
    this.count.set(this.count.get() + 1);
  }
}

// Elsewhere in your app...

countPlusTen.get(); // 10
CountPlusTen.on(StateEvents.CHANGE, (stateReference) => {
  stateReference.get(); // 10;
})

collection(storeReference: Object | any, computeFunction: function)

countPlusTen = computed(MyStore, () => {
  return MyStore.count.get() + 10;
});

Create a new instance of computed state. This often exists inside a global store.

Computed.get()

Returns the value of the computed state.

Computed.subscribe(callback: Function)

collection.subscribe(computedChangeHandler);

function computedChangeHandler(stateReference){
  console.log('Computed changed!')
}

Subscribe a function to any changes in the computed state.

Computed.on(eventName: string, payload: any)

collection.on(StoreEvents.ADDED, addHandler);

function addHandler(collectionReferece){
    console.log('Item added!')
}

Add an event listener to your store. The available collection event to dispatch is:

CollectionEvents.CHANGED

Collection.off(eventName: string, handlerFunction: Function)

collection.off(CollectionEvents.CHANGED, changeHandler);

Remove an event listener from your computed state.

📬 Subscribing

Subscriber Component

Subscriber(Component: Class)

You can turn your component into a Subscriber so that it's easier to manage subscribing to state.

Using a different framework's component? Make sure it contains either a .forceUpdate method or .render method.

this.subscribe(stateReference: State | Store | Collection | Computed, stateReference, ...)

// In Class scope
this.subscribe(count, title);

Subscribe to a single or multiple states. If either are changed, your component will be updated.

Example:

import myGlobalStore from './stores/myGlobalStore';

class CountExample extends Subscriber(React.Component) {
  count = state<number>(0);

  componentWillUnmount = () => {
    this.unsubscribeAll(); // Unsubscribe from all state you've subscribed to before unmounting.
  }

  render() {
    this.subscribe(count, myGlobalStore); // Subscribe to your state.
    return (
      <div>
          <h1>{myGlobalStore.title.get()}</h1>
          Clicked: {this.count.get()} times {' '}
          <button onClick={this.increment}>+</button>{' '}
      </div>
    );
  }
}

Subscribe Hook

subscribe(stateReference: State | Store | Collection | Computed, stateReference, ...)

You can subscribe to state in your functional React component using the custom subscribe hook.

// In Function scope
subscribe(count, title);

Subscribe to a single or multiple states. If either are changed, your component will be updated.

Example:

import myGlobalStore from './stores/myGlobalStore';

let count = state<number>(0);

function Count() {
  subscribe(count, myGlobalStore); // Subscribe to the state using the subscribe hook.

  function increment() {
    count.set(count.get() + 1);
  }

  return (
    <div>
        <h1>{myGlobalStore.title.get()}</h1>
        <p>Clicked: {count.get()} times {' '}<p>
        <button onClick={increment}>+</button>{' '}
    </div>
  );
}

🤖 Testing

test('counter', t => {
  let store = new CounterStore();
  t.assert(store.count.get() === 0);

  store.increment();
  t.assert(store.count.get() === 1);

  t.end();
})