@vutr/gstate
TypeScript icon, indicating that this package has built-in type declarations

2.0.2 • Public • Published

Logo

Manage React global/shared state while preserving nature by writing less CODE!
Explore the docs »

· Report Bug · Request Feature

There has been numerous open-source projects that try to tackle the problem with React shared state, but here is a bad news: most of them are overly complicated, with unfriendly API and introduce way too much brain-load as well as boilerplate code.

What if we can have a Library with minimal APIs and flat-learning curve that will not take us more than 5 minutes to grasp? One of the few best stuffs that may fall in such cateogry I would honor here is zustand and redux-zero. Redux-zero is great for those who love to stick with Redux's style, while zustand is nice for modern adopters who favor react-hooks.

Yet though I love hooks, I don't feel quite right with zustand, meanwhile I'm done with Redux for ages already. Well that means I have to do it myself - the way I like. And I hope you would like it as well.

Table of Contents

  1. About The Project
  2. Getting Started
  3. Usage
  4. APIs
  5. Contributing
  6. License
  7. Contact

About The Project

GreenState is a state-management library that helps develop shared or global state between components using the sweet React-Hooks. The Aim is to provide one of the friendlies API that - as Steve Job used to say - It just works!.

Let's say, you declare **Your State**, ask a key name - that's simple -  and GreenState returns just its value to you??

You can use it to create as many stores as you like. The APIs are minmal, support all basic cases, including..

  1. Get a single value from Store
  2. Get a group of values from Store
  3. Get a derived-value / composed-value from Store (just like you would with RecoilSelector)
  4. Dispatch an action to update store - either async or non-asynchronous
  5. Intuitive type-hint support with typescript!
  6. Maximum coverage!

Built With

  • typescript
  • that's it!

Getting Started

Prerequisites

This is a React stuff, so naturally you need React - more specifically the version that supports hook - unless you are Gandalf the Grey who can pretty much do whatever he likes without npm!

Installation

Install NPM packages

npm install @vutr/gstate

Usage

  • First, setup your state store with GreenState
// ./state
import { Store } from '@vutr/gstate'

type GlobalState = {
  count: number
  hello: string
}

const state: GlobalState = {
  count: 0,
  hello: 'World'
}

const store = new Store(state)

export const useGValue = store.useGValue
export const useGState = store.useGState
export const useAction = store.useAction
  • Second, use The-Damn-Store!! Your precious state will be shared between components.
// ./component
import { useGValue, useGState, useAction } from './state'

const TestOne = () => {
  const [cnt, setCnt] = useGValue('count')
  const handleClick = () => setCnt(cnt + 1)
  return (
	<div>
	  <button onClick={handleClick} data-testid="inc-1">Increase</button>
	  <p data-testid="message">{`count = ${cnt}`}</p>
	</div>
  )
}

const TestTwo = () => {
  const [obj, setObj] = useGState('count', 'hello')
  const handleClick = () => setObj({ count: cnt.count + 3 })
  return (
	<div>
	  <button onClick={handleClick} data-testid="inc-3">
		Increase
	  </button>
	</div>
  )
}

APIs

Store(intialState: Record<string, any>, derivedState?: (state) => Record<string, any>)

  • Aside from defining initialState, you can optionally have derivedState passed to Store. The derivedState is something just like RecoilSelector, or React's getDerivedState
  • Combine everything we have something like...
import { Store } from '@vutr/gstate'
type State = {
  count1: number
  count2: number
}

type DerivedState = {
  total: number
  divided: number
}

const state: State = {
  count1: 1,
  count2: 1,
}

const derived = (state: State): DerivedState => ({
  total: state.count1 + state.count2,
  divided: state.count1 / state.count2,
})

const store = new Store(state, derived)

export const useGValue = store.useGValue
export const useGState = store.useGState
export const useAction = store.useAction
export const useDerivedValue = store.useDerivedValue
export const useDerivedState = store.useDerivedState

useGValue(key: keyof State) => [State[key], Dispatch<State[key]>]

  • get and set a single value from State using its key.
import { useGValue } from './state'
const [cnt, setCnt] = useGValue('count')

console.log(cnt) // 0
console.log(setCnt) // Function(x: number) => void; type inferred from the value of `count`
setCnt(1)
console.log(cnt) // 1

useGState(...keys: keyof State[]) => [Pick<State, keys>, (obj: Partial) => void]

  • get and set a collection of value from State using their keys.
import { useGValue } from './state'
const [obj, setObj] = useGState('count', 'hello')
console.log(obj) // { count: 0, hello: 'World'}
console.log(setObj) // Function(x: Partial<State>) => void
// type inferred from the value of `keys` passed to useGState

useAction(action: Action<State, args>) => (args, { get, set }) => Partial | void

type Action<T extends StateObj, K> = (value: K, { get: StateGetter<T>, set: StateSetter<T> }) => (
  Partial<T> | void | Promise<Partial<T> | void>
)
  • Define an action and use it to update the State
  • If an action return part of the State, it will be merged to the State, otherwise nothing will happen
  • Action can be either asynchronous or synchronous - meaning async/await works just fine
  • get State or set State freely
const { useAction, useGValue, StateSetter } = new Store(state)

const increase = (num, { count }) => {
  return { count: count + num }
}

const asyncIncrease = async (num: number, { get, set }) => {
  await sleep(300)
  set({ count: get().count + num })
}

let greet = ''
const changeGreet: Action<GlobalState, string> = (country, { set }) => {
  greet = `Hello ${country}`
  set({ hello: 'Ciao' })
}

const TestOne = () => {
  const cnt = useGValue('count')[0]
  const inc = useAction(increase)
  const asinc = useAction(asyncIncrease)
  const greeting = useAction(changeGreet)

  const clickAsync = () => asinc(3)
  const handleClick = () => inc(4)
  const makeGreet = () => greeting('Vietnam')

  return (
	<div>
	  <button onClick={handleClick} data-testid="inc-4">Increase</button>
	  <button onClick={makeGreet} data-testid="greet">Greet</button>
	  <button onClick={clickAsync} data-testid="asinc-3">Async</button>
	  <p data-testid="message">{`count = ${cnt}`}</p>
	</div>
  )
}
  • useDerivedValue(key: K extends keyof DerivedState) => DerivedState[K]

  • useDerivedState(...keys: Array) => Pick<DerivedState, keys>

Example

type State = {
  count1: number
  count2: number
}

type DerivedState = {
  total: number
  divided: number
}

const state: State = {
  count1: 1,
  count2: 1,
}

const derived = (state: State): DerivedState => ({
  total: state.count1 + state.count2,
  divided: state.count1 / state.count2,
})

const { useGValue, useDerivedValue, useDerivedState } = new Store(state, derived)

const TestOne = () => {
  const total = useDerivedValue('total')
  const totalObject = useDerivedState('total', 'divided')

  return (
	<div>
	  <p data-testid="total">{`total = ${total}`}</p>
	  <p data-testid="derived">{`derived = ${totalObject.divided}`}</p>
	</div>
  )
}

Contributing

Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated.

License

Distributed under the MIT License. See LICENSE for more information.

Contact

Vu Tran - me@vutr.io

Project Link: https://github.com/vutran1710/Green-State

Package Sidebar

Install

npm i @vutr/gstate

Weekly Downloads

13

Version

2.0.2

License

MIT

Unpacked Size

16.6 kB

Total Files

12

Last publish

Collaborators

  • vutr