Skip to content

vyquocvu/anystate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

93 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

anyState

npm version License: MIT Tests

What is anyState?

anyState is a tiny, framework-agnostic state management library that works with any frontend framework!

Key Features

  • Simple Object-based API: State is just an observable object
  • Path-based Updates: Update nested values using simple path notation
  • Change Watching: Subscribe to state changes with callback functions
  • Framework Independent: Works with React, Vue, Svelte, Solid, or any framework
  • Tiny Bundle Size: Under 5KB minified
  • TypeScript Support: Full TypeScript definitions included

Back to basics - anyState uses callback functions to handle state changes. Each change is identified by a unique path string to ensure precise state updates. This allows you to track any changes from anywhere in your application.

Installation

npm install anystate
yarn add anystate
pnpm add anystate

Roadmap

  • Initialize anyState
  • Simple state management
  • getItem/setItem with path notation
  • Watch onChange functionality
  • Vue-like multiple property watching
  • TypeScript support
  • React hooks integration
  • Vue composables
  • Svelte stores compatibility
  • Persistence plugins

Usage

Basic Setup

Import and initialize anyState with createStore():

import { createStore } from 'anystate';

const store = createStore({
  user: {
    name: 'John',
    age: 30,
    preferences: {
      theme: 'dark'
    }
  },
  todos: [
    { id: 1, text: 'Learn anyState', completed: false },
    { id: 2, text: 'Build awesome app', completed: false }
  ]
});

State Management

Get entire state

const state = store.getState();
console.log(state); // Returns the entire state object

Set entire state

store.setState({
  user: {
    name: 'Jane',
    age: 25,
    preferences: {
      theme: 'light'
    }
  },
  todos: []
});

Path-based Operations

anyState supports dot notation and array indexing for nested operations:

Set nested values

// Update simple property
store.setItem('user.name', 'Alice');

// Update nested object property
store.setItem('user.preferences.theme', 'auto');

// Update array item
store.setItem('todos[0].completed', true);

// Update nested array property
store.setItem('todos[0].text', 'Learn anyState βœ“');

Get nested values

// Get simple property
const userName = store.getItem('user.name');

// Get nested object
const preferences = store.getItem('user.preferences');

// Get array item
const firstTodo = store.getItem('todos[0]');

// Get nested array property
const isCompleted = store.getItem('todos[0].completed');

Watching State Changes

Watch single property

store.watch('user.name', (newValue, oldValue) => {
  console.log(`User name changed from ${oldValue} to ${newValue}`);
});

Watch multiple properties (Vue-like syntax)

store.watch({
  'user.name': (newValue, oldValue) => {
    console.log(`Name: ${oldValue} β†’ ${newValue}`);
  },
  'user.age': (newValue, oldValue) => {
    console.log(`Age: ${oldValue} β†’ ${newValue}`);
  },
  'todos[0].completed': (newValue, oldValue) => {
    console.log(`First todo completed: ${newValue}`);
  }
});

Watch complex objects

// Watch entire user object
store.watch('user', (newUser, oldUser) => {
  console.log('User object changed:', { newUser, oldUser });
});

// Watch array changes
store.watch('todos', (newTodos, oldTodos) => {
  console.log(`Todo count: ${oldTodos?.length} β†’ ${newTodos?.length}`);
});

API Reference

createStore(initialState)

Creates a new anyState store instance.

Parameters:

  • initialState (Object): The initial state object

Returns: Store instance with the following methods:

getState()

Returns the entire current state.

setState(newState)

Replaces the entire state with a new state object.

Parameters:

  • newState (Object): The new state object

getItem(path)

Gets a value at the specified path.

Parameters:

  • path (string): Dot notation path (e.g., 'user.name', 'items[0].title')

Returns: The value at the specified path

setItem(path, value)

Sets a value at the specified path.

Parameters:

  • path (string): Dot notation path
  • value (any): The value to set

watch(pathOrObject, callback?)

Watches for changes at specified paths.

Parameters:

  • pathOrObject (string | Object): Path string or object with path-callback pairs
  • callback (Function): Callback function for string paths (newValue, oldValue) => void

React Hooks

useAnyState(store, path)

React hook for subscribing to store values.

Parameters:

  • store (Store): anyState store instance
  • path (string): Dot notation path to watch

Returns: [value, setValue] tuple similar to React's useState

useAnyStateMultiple(store, paths)

React hook for subscribing to multiple store values.

Parameters:

  • store (Store): anyState store instance
  • paths (Object): Object mapping property names to paths

Returns: Object with values and setter functions

Vue Composables

useAnyStateVue(store, path)

Vue composable for subscribing to store values.

Parameters:

  • store (Store): anyState store instance
  • path (string): Dot notation path to watch

Returns: [ref, setValue] tuple with reactive ref and setter function

useAnyStateMultipleVue(store, paths)

Vue composable for subscribing to multiple store values.

Parameters:

  • store (Store): anyState store instance
  • paths (Object): Object mapping property names to paths

Returns: Reactive object with values and setter functions

useAnyStateComputed(store, paths, computeFn)

Vue composable for creating computed properties from store values.

Parameters:

  • store (Store): anyState store instance
  • paths (Array): Array of paths to watch
  • computeFn (Function): Function to compute derived value

Returns: Computed ref

Svelte Stores

createAnyStateStore(store, path)

Creates a Svelte writable store from an anyState path.

Parameters:

  • store (Store): anyState store instance
  • path (string): Dot notation path to watch

Returns: Svelte writable store with subscribe, set, update, and destroy methods

createAnyStateStores(store, paths)

Creates multiple Svelte stores from anyState paths.

Parameters:

  • store (Store): anyState store instance
  • paths (Object): Object mapping store names to paths

Returns: Object with Svelte writable stores

createAnyStateDerived(store, paths, deriveFn)

Creates a Svelte derived store from multiple anyState paths.

Parameters:

  • store (Store): anyState store instance
  • paths (Array): Array of paths to watch
  • deriveFn (Function): Function to derive the value

Returns: Svelte derived store

createAnyStateReadable(store, path)

Creates a Svelte readable store from an anyState path.

Parameters:

  • store (Store): anyState store instance
  • path (string): Dot notation path to watch

Returns: Svelte readable store

Persistence Plugins

addPersistence(store, options)

Adds persistence capabilities to an anyState store.

Parameters:

  • store (Store): anyState store instance
  • options (Object): Persistence configuration
    • plugins (Array): Array of persistence plugins (default: [localStoragePlugin()])
    • paths (Array): Specific paths to persist (default: [] - all state)
    • throttle (number): Save throttle in milliseconds (default: 1000)
    • autoSave (boolean): Enable automatic saving on changes (default: true)

Returns: Object with save(), load(), clear(), and destroy() methods

Built-in Plugins

localStoragePlugin(key)

Persists state to browser localStorage.

sessionStoragePlugin(key)

Persists state to browser sessionStorage.

indexedDBPlugin(dbName, storeName, key)

Persists state to browser IndexedDB.

createCustomPlugin(name, load, save, clear)

Creates a custom persistence plugin.

Framework Integration Examples

React Hooks Integration

anyState now provides built-in React hooks for seamless integration:

useAnyState(store, path)

A React hook that subscribes to a specific path in the store and returns a stateful value and a setter function.

import { createStore, useAnyState } from 'anystate';

const store = createStore({ 
  user: { name: 'John', age: 30 },
  todos: []
});

function UserComponent() {
  const [name, setName] = useAnyState(store, 'user.name');
  const [age, setAge] = useAnyState(store, 'user.age');
  
  return (
    <div>
      <h2>{name} ({age} years old)</h2>
      <button onClick={() => setAge(age + 1)}>
        Birthday! πŸŽ‚
      </button>
    </div>
  );
}

useAnyStateMultiple(store, paths)

For watching multiple values at once:

function UserForm() {
  const userData = useAnyStateMultiple(store, {
    name: 'user.name',
    age: 'user.age',
    email: 'user.email'
  });
  
  return (
    <form>
      <input 
        value={userData.name} 
        onChange={(e) => userData.setName(e.target.value)} 
      />
      <input 
        value={userData.age} 
        onChange={(e) => userData.setAge(e.target.value)} 
      />
      <input 
        value={userData.email} 
        onChange={(e) => userData.setEmail(e.target.value)} 
      />
    </form>
  );
}

React Hook Example

import { createStore, useAnyState } from 'anystate';

const store = createStore({ count: 0 });

function Counter() {
  const [count, setCount] = useAnyState(store, 'count');
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

// You can also use the hook with nested paths
function UserProfile() {
  const [name, setName] = useAnyState(store, 'user.name');
  const [age, setAge] = useAnyState(store, 'user.age');
  
  return (
    <div>
      <input 
        value={name} 
        onChange={(e) => setName(e.target.value)} 
        placeholder="Name" 
      />
      <input 
        type="number" 
        value={age} 
        onChange={(e) => setAge(parseInt(e.target.value))} 
        placeholder="Age" 
      />
    </div>
  );
}

Vue Composables Integration

anyState provides Vue 3 composables for seamless integration with Vue's reactivity system:

useAnyStateVue(store, path)

A Vue composable that creates a reactive ref for a store value.

<template>
  <div>
    <h2>{{ name }} ({{ age }} years old)</h2>
    <button @click="birthday">Birthday! πŸŽ‚</button>
  </div>
</template>

<script setup>
import { createStore, useAnyStateVue } from 'anystate';

const store = createStore({ 
  user: { name: 'John', age: 30 }
});

const [name, setName] = useAnyStateVue(store, 'user.name');
const [age, setAge] = useAnyStateVue(store, 'user.age');

const birthday = () => setAge(age.value + 1);
</script>

useAnyStateMultipleVue(store, paths)

For managing multiple store values:

<script setup>
import { useAnyStateMultipleVue } from 'anystate';

const userData = useAnyStateMultipleVue(store, {
  name: 'user.name',
  age: 'user.age',
  email: 'user.email'
});

// Access values: userData.name.value, userData.age.value
// Set values: userData.setName('New Name'), userData.setAge(25)
</script>

useAnyStateComputed(store, paths, computeFn)

For computed values derived from store data:

<script setup>
import { useAnyStateComputed } from 'anystate';

const fullName = useAnyStateComputed(
  store,
  ['user.firstName', 'user.lastName'],
  (first, last) => `${first} ${last}`
);

// fullName.value will automatically update when firstName or lastName change
</script>

Vue Composition API Example (Manual Integration)

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { createStore } from 'anystate';

const store = createStore({ count: 0 });
const count = ref(store.getItem('count'));

let unwatch;

onMounted(() => {
  unwatch = store.watch('count', (newValue) => {
    count.value = newValue;
  });
});

onUnmounted(() => {
  if (unwatch) unwatch();
});

const increment = () => {
  store.setItem('count', store.getItem('count') + 1);
};
</script>

Vue Composition API Example (With Composables)

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script setup>
import { createStore, useAnyStateVue } from 'anystate';

const store = createStore({ count: 0 });
const [count, setCount] = useAnyStateVue(store, 'count');

const increment = () => {
  setCount(count.value + 1);
};
</script>

Svelte Stores Integration

anyState provides seamless integration with Svelte's store system:

createAnyStateStore(store, path)

Creates a Svelte writable store that stays in sync with anyState.

<script>
  import { createStore, createAnyStateStore } from 'anystate';
  
  const store = createStore({ 
    user: { name: 'John', age: 30 }
  });
  
  const name = createAnyStateStore(store, 'user.name');
  const age = createAnyStateStore(store, 'user.age');
  
  function birthday() {
    age.update(current => current + 1);
  }
</script>

<div>
  <h2>{$name} ({$age} years old)</h2>
  <button on:click={birthday}>Birthday! πŸŽ‚</button>
</div>

createAnyStateStores(store, paths)

For managing multiple stores:

<script>
  import { createAnyStateStores } from 'anystate';
  
  const { name, age, email } = createAnyStateStores(store, {
    name: 'user.name',
    age: 'user.age', 
    email: 'user.email'
  });
</script>

<form>
  <input bind:value={$name} placeholder="Name" />
  <input bind:value={$age} type="number" placeholder="Age" />
  <input bind:value={$email} type="email" placeholder="Email" />
</form>

createAnyStateDerived(store, paths, deriveFn)

For computed/derived values:

<script>
  import { createAnyStateDerived } from 'anystate';
  
  const fullName = createAnyStateDerived(
    store,
    ['user.firstName', 'user.lastName'],
    (first, last) => `${first} ${last}`
  );
</script>

<p>Welcome, {$fullName}!</p>

createAnyStateReadable(store, path)

For read-only stores:

<script>
  import { createAnyStateReadable } from 'anystate';
  
  const status = createAnyStateReadable(store, 'app.status');
</script>

<div class="status-{$status}">
  Status: {$status}
</div>

Persistence

anyState provides flexible persistence plugins to save and restore state:

Basic LocalStorage Persistence

import { createStore, addPersistence, localStoragePlugin } from 'anystate';

const store = createStore({ 
  user: { name: 'John', preferences: { theme: 'dark' } },
  todos: []
});

// Add persistence
const persistence = await addPersistence(store, {
  plugins: [localStoragePlugin('my-app-state')],
  autoSave: true,
  throttle: 1000
});

// Load existing state
await persistence.load();

// State changes are automatically saved to localStorage
store.setItem('user.name', 'Jane'); // Saved after 1 second

Multiple Storage Backends

// Use multiple storage plugins with fallback
const persistence = await addPersistence(store, {
  plugins: [
    indexedDBPlugin('myapp', 'state', 'appstate'),
    localStoragePlugin('my-app-backup'),
    sessionStoragePlugin('my-app-session')
  ]
});

Selective Persistence

// Only persist specific paths
const persistence = await addPersistence(store, {
  paths: ['user.preferences', 'todos'],
  plugins: [localStoragePlugin('user-data')]
});

Custom Storage Plugin

// Create a custom plugin (e.g., for server storage)
const serverPlugin = createCustomPlugin(
  'server',
  async () => {
    const response = await fetch('/api/state');
    return response.json();
  },
  async (state) => {
    await fetch('/api/state', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(state)
    });
  },
  async () => {
    await fetch('/api/state', { method: 'DELETE' });
  }
);

const persistence = await addPersistence(store, {
  plugins: [serverPlugin]
});

Manual Control

// Disable auto-save for manual control
const persistence = await addPersistence(store, {
  autoSave: false,
  plugins: [localStoragePlugin()]
});

// Manual operations
await persistence.save();  // Save current state
await persistence.load();  // Load saved state
await persistence.clear(); // Clear saved state
persistence.destroy();     // Clean up watchers

Examples

Explore complete working examples in different frameworks:

Each example demonstrates:

  • State initialization and management
  • Path-based updates for nested data
  • Change watching and UI reactivity
  • Framework-specific integration patterns

Resources

  • Interactive Examples & Playground - Coming Soon
  • Video Tutorials - Coming Soon
  • Advanced Patterns Guide - Coming Soon

Development

Setup

# Install dependencies
npm install

# Run tests
npm test

# Development mode (TypeScript watch)
npm run dev

# Build for production
npm run build

Project Structure

β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ index.ts        # Main library code
β”‚   └── type.d.ts       # TypeScript definitions
β”œβ”€β”€ test/               # Test files
β”œβ”€β”€ examples/           # Framework examples
β”‚   β”œβ”€β”€ todo-react/     # React example
β”‚   β”œβ”€β”€ todo-solid/     # Solid example
β”‚   └── todo-svelte/    # Svelte example
└── dist/              # Built files

Running Examples

# React example
cd examples/todo-react && npm install && npm start

# Solid example  
cd examples/todo-solid && npm install && npm start

# Svelte example
cd examples/todo-svelte && npm install && npm run dev

Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature-name
  3. Make your changes and add tests
  4. Run tests: npm test
  5. Commit your changes: git commit -am 'Add feature'
  6. Push to the branch: git push origin feature-name
  7. Submit a pull request

License

MIT License - see LICENSE.md for details.

About

anystate is a tiny state management library for any framework!

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published