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.
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..
- Get a single value from Store
- Get a group of values from Store
- Get a derived-value / composed-value from Store (just like you would with RecoilSelector)
- Dispatch an action to update store - either async or non-asynchronous
- Intuitive type-hint support with typescript!
- Maximum coverage (100%)!
- typescript
- that's it!
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
!
Install NPM packages
npm install @vutr/gstate
- 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>
)
}
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>
)
}
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.
Distributed under the MIT License. See LICENSE
for more information.
Vu Tran - [email protected]
Project Link: https://github.com/vutran1710/Green-State