Sangte is a state management library for React. This library is inspired by Redux Toolkit and Recoil.
Sangte means "state" in Korean.
To install the library, run the following command:
npm install sangte
Or if you're using yarn:
yarn add sangte
- Less boilerplate
- Rerender only when the state you're using is updated
- Easy to use
- Allows multiple providers
- TypeScript support
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: 'john@email.com' }, (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
},
}))
The library provides hooks to utilize the state or actions from your components.
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
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: 'john@email.com' })
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>
}
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>
)
}
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>
)
}
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>
)
}
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>
}
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>
)
}
Docs are still in progress. If you have any questions, please open an issue.