sangte
Sangte is a state management library for React. This library is inspired by Redux Toolkit and Recoil.
Sangte means "state" in Korean.
Installation
To install the library, run the following command:
npm install sangte
Or if you're using yarn:
yarn add sangte
Why sangte?
- Less boilerplate
- Rerender only when the state you're using is updated
- Easy to use
- Allows multiple providers
- TypeScript support
Usage
First create a state
To create a state you need to use the sangte
function. A state of sangte should have a default value, and actions to update the state. Actions are optional.
Sangte uses immer internally to update the state. So you can mutate the state directly while keeping the immutability.
import { sangte } from 'sangte'
const counterState = sangte(0)
const textState = sangte('text')
const userState = sangte({ id: 1, name: 'John', email: '[email protected]' }, (prev) => ({
setName(name: string) {
prev.name = name
},
setEmail(email: string) {
return {
...prev,
email,
}
},
}))
interface Todo {
id: number
text: string
done: boolean
}
const todosState = sangte<Todo[]>([], (prev) => ({
add(todo: Todo) {
return prev.push(todo)
},
remove(id: number) {
return prev.filter((todo) => todo.id !== id)
},
toggle(id: number) {
const todo = prev.find((todo) => todo.id === id)
todo.done = !todo.done
},
}))
Use the state or actions from your components
The library provides hooks to utilize the state or actions from your components.
useSangte
useSangte
works like useState
from React, but it works globally. It returns the state and a setter function to update the state.
import { sangte, useSangte } from 'sangte'
const counterState = sangte(0)
function Counter() {
const [counter, setCounter] = useSangte(counterState)
return (
<div>
<h1>{counter}</h1>
<button onClick={() => setCounter((prev) => prev + 1)}>Increment</button>
<button onClick={() => setCounter(0)}>Reset</button>
</div>
)
}
export default Counter
useSangteValue
If you only need the value of the state, you can use useSangteValue
.
import { useSangteValue } from 'sangte'
const counterState = sangte(0)
function CounterValue() {
const counter = useSangteValue(counterState)
return <h1>{counter}</h1>
}
If you want to select a part of the state, you can pass selector function as second argument. If you select multiple fields, the component will rerender after shallow comparison. You can override the comparison function by passing a custom equality function to third argument.
import { sangte, useSangteValue } from 'sangte'
const userState = sangte({ id: 1, name: 'John', email: '[email protected]' })
function User() {
const { name, email } = useSangteValue(userState, (state) => ({
name: state.name,
email: state.email,
}))
return (
<div>
<h1>{name}</h1>
<h2>{email}</h2>
</div>
)
}
If you want to use a memoized selector that is prcessed only when its dependencies update, you can create a read-only Sangte as below.
import { sangte } from 'sangte'
const todosState = sangte([
{ id: 1, text: 'Basic usage', done: true },
{ id: 21, text: 'Ready-only sangte', done: false },
])
const undoneTodosValue = sangte((get) => get(todosState).filter((todo) => !todo.done))
function UndoneTodos() {
const undoneTodos = useSangteValue(undoneTodosValue)
return <div>{undoneTodos.length} todos undone.</div>
}
useSetSangte
If you only need the updater function of the state, you can use useSetSangte
.
import { sangte, useSetSangte } from 'sangte'
const counterState = sangte(0)
function CounterButtons() {
const setCounter = useSetSangte(counterState)
return (
<div>
<button onClick={() => setCounter((prev) => prev + 1)}>Increment</button>
</div>
)
}
useSangteActions
If you have defined actions for the state, you can use useSangteActions
to get the actions.
import { sangte, useSangteActions } from 'sangte'
const counterState = sangte(0, (prev) => ({
increase() {
return prev + 1
},
decreaseBy(amount: number) {
return prev - amount
},
}))
function CounterButtons() {
const { increase, decreaseBy } = useSangteActions(counterState)
return (
<div>
<button onClick={increase}>Increase</button>
<button onClick={() => decreaseBy(10)}>Decrease</button>
</div>
)
}
useResetSangte
If you want to reset the state to its default value, you can use useResetSangte
.
import { sangte, useResetSangte } from 'sangte'
const counterState = sangte(0)
function Counter() {
const [counter, setCounter] = useSangte(counterState)
const resetCounter = useResetSangte(counterState)
return (
<div>
<h1>{counter}</h1>
<button onClick={() => setCounter((prev) => prev + 1)}>Increment</button>
<button onClick={resetCounter}>Reset</button>
</div>
)
}
useResetAllSangte
If you want to reset all the states to their default values, you can use useResetAllSangte
. This hook also resets sangte inside the nested providers.
import { sangte, useResetAllSangte } from 'sangte'
const counterState = sangte(0)
const textState = sangte('text')
function Counter() {
const [counter, setCounter] = useSangte(counterState)
const [text, setText] = useSangte(textState)
const resetAll = useResetAllSangte()
return (
<div>
<h1>
{counter} | {text}
</h1>
<button onClick={() => setCounter((prev) => prev + 1)}>Increment</button>
<button onClick={() => setText('Hello World'}>Update Text</button>
<button onClick={resetAll}>Reset</button>
</div>
)
}
If you want to reset the states globally (including all parent providers), you can pass true
as first argument.
import { sangte, useResetAllSangte } from 'sangte'
function RestAll() {
const resetAll = useResetAllSangte()
return <button onClick={() => resetAll(true)}>Reset All</button>
}
useSangteCallback
If you want to use the state in a callback but you do not want to rerender the component as the state changes, you can use useSangteCallback
.
import { sangte, useSangteCallback } from 'sangte'
const valueState = sangte('hello world!')
function ConfirmButton() {
// this component won't rerender even when valueState changes
const confirm = useSangteCallback(({ get }) => {
const value = get(valueState)
console.log(value) // do something with value..
}, [])
return <button onClick={confirm}>Confirm</button>
}
You can also use the setter function or actions with useSangteCallback
.
import { sangte, useSangteCallback } from 'sangte'
const counterState = sangte(0, (prev) => ({
add(value: number) {
return prev + value
},
}))
function Counter() {
// calls add action
const add2 = useSangteCallback(({ actions }) => {
const { add } = actions(counterState)
add(2)
}, [])
// sets counterState to 10000
const set10000 = useSangteCallback(({ set }) => {
set(counterState, 10000)
}, [])
return (
<div>
<button onClick={add2}>add 2</button>
<button onClick={logCount}>logCount</button>
<button onClick={set10000}>set 10000</button>
</div>
)
}
Recipe
Using multiple providers
Inheriting state from parent provider
Server side rendering
Docs are still in progress. If you have any questions, please open an issue.