FireworksX / graph-state

Cache state manager.
2 stars 0 forks source link
[![npm](https://img.shields.io/npm/v/@graph-state/core?style=flat-square)](https://www.npmjs.com/package/@graph-state/core) ![npm type definitions](https://img.shields.io/npm/types/@graph-state/core?style=flat-square) [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@graph-state/core?style=flat-square)](https://bundlephobia.com/result?p=@graph-state/core)

@graph-state

graph-state is graph based state manager, designed for both simple and complex applications. Focused on work with many deep and dependence data.

Packages

Package Version Docs Size
@graph-state/core npm npm bundle size
@graph-state/react npm npm bundle size
@graph-state/plugin-logger npm npm bundle size
@graph-state/plugin-ws npm npm bundle size
@graph-state/plugin-extend npm npm bundle size

Examples

Get started

Install

npm i @graph-state/core @graph-state/react

or

yarn add @graph-state/core @graph-state/react

Usage

App.tsx

import { useGraph } from '@graph-state/react'
import { createState } from '@graph-state/core'

const grapState = createState({
  value: 150
})

const App = () => {
  const [state, setState] = useGraph(grapState)

  return <div>
    <h1>{state.value}</h1>
    <input type="text" value={state.value} onChange={(e) => setState({ value: e.target.value })}/>
  </div>
}

Base state

const state = createState({
  initialState: {
    modal: 'about',
    layers: []
  }
})

state.subscribe(state, (data) => {
  // state updates
})

state.resolve() // { modal: 'about', layers: [] }
// or state.resolve(state) or state.resolve(state.key)

state.mutate({
  layers: [19]
})

state.resolve() // { modal: 'about', layers: [19] }

Concept

Graph state had few abstractions


Graph

Is plain object with _type

const baseGraph = {
  _type: 'Graph',
  _id: 'any uniq id' // or id or use keying config
}

State

Is a manager of graphs with reactive updates.

import { createState } from '@graph-state/core'

const graphState = createState()

console.log(graphState)
/**
 * _id: 'State uniq id'
 * _type: 'Instance'
 * ...other methods
 */

State mutate method

Signature

import { createState } from '@graph-state/core'

const graphState = createState()

const user = {
  _type: 'User',
  _id: 'usernameOne',
  name: 'James'
}

graphState.mutate(user)

// Now graphState has user, you can read him
graphState.resolve(user).name // James

// Change name
graphState.mutate({
  ...user,
  name: 'Not James'
})
graphState.resolve(user).name // Not James

// Change by key
graphState.mutate('User:usernameOne', {
  name: 'Alie',
  gender: 'female' // Add new property
})

graphState.resolve(user)
/**
 * _type: 'User'
 * _id: 'usernameOne'
 * name: 'James'
 * gender: 'female'
 */

// Mutate with conditional
graphState.mutate('User:usernameOne', prev => ({
  someProp: prev.gender === 'female' ? 'value one' : 'value two'
}))

By default, state deep merged. You don`t need spread every update. You can use {replace: true} on mutation for replace state.

Mutate options


State resolve value and inspect fields

import { createState } from '@graph-state/core'

const userOne = {
  _type: 'User',
  _id: 'id',
  name: 'James'
}

const userTwo = {
  _type: 'User',
  _id: 'id2',
  name: 'Barb'
}
const graphState = createState({initialState: [userOne, userTwo]})

graphState.resolve(userOne).name // James
graphState.resolve('User:id2').name // Barb

graphState.inspectFields('User')
/**
 * User:id
 * User:id2
 */

Resolve options

Observe updates

Simple variant


import { createState } from '@graph-state/core'

const userGraph = {
  _type: 'User',
  _id: 'id',
  name: 'James'
}

const graphState = createState({initialState: userGraph})

graphState.subscribe(userGraph, (nextState) => {
  /**
   * _type
   * _id
   * name: Stef
   */
})

graphState.mutate({
  ...userGraph,
  name: 'Stef'
})

Subscribe on all state

import { createState } from '@graph-state/core'

const userGraph = {
  _type: 'User',
  _id: 'id',
  name: 'James'
}

const graphState = createState({initialState: userGraph})

graphState.subscribe(nextState => {
  // Call every state update
})

graphState.mutate({
  ...userGraph,
  name: 'Stef'
})

Nested graph

import { createState } from '@graph-state/core'

const user = {
  _type: 'User',
  _id: 'id',
  name: 'Vlad'
}

const post = {
  _type: 'Post',
  _id: 'id',
  title: 'Some post title',
  auhtor: user
}

const graphState = createState({initialState: post})

graphState.subscribe(user, (nextState) => {
  /**
   * user will be updated if parent was chnaged
   * 
   * _type
   * _id
   * name: Stef
   */
})

graphState.mutate({
  ...post,
  title: 'Some different title'
})

Nested graph state

An example above we create nested graph. You can use any nested level for your state. Inside state will be created link for graphs.

Document
  posts
    post (_id one)
      author (_id someUser)
    post
      author (_id otherUser)
    post
      author (_id someUser) // Same user

You can mutate user in one place and him will update in all posts.

Plugins

import { createState } from '@graph-state/core'

const loggerPlugin = (graphState) => {
  const originalMutate = graphState.mutate

  graphState.mutate = (...args) => {
    const {entityKey, data} = getArgumentsForMutate(...args)

    console.log(`Graph ${entityKey} was updated.`)
    originalMutate(...args)
  }
}

const graphState = createState({
  plugins: [loggerPlugin]
})

Skip

Sometimes we need skip recursive iterate and save link on initial data.

const Title = <h1>Hello</h1>

const state = createState({
  initialState: {
    jsxNode: Title
  },
  skip: [() => /*check if jsx*/]
})

state.resolve().jsxNode === Title // true

Utility

keyOfEntity

graphState.keyOfEntity({
  _type: 'User',
  _id: 'id'
}) // User:id

entityOfKey

graphState.entityOfKey('User:id')
/**
 * _type: 'User',
 * _id: 'id'
 */