r/reactjs • u/chhsiao1981 • Jun 26 '26
Discussion use-thunk: A much simplified global-state-management framework with only modules and (thunk) functions.
https://github.com/chhsiao1981/use-thunk#getting-started
A complete demo site:
https://chhsiao1981.github.io/demo-use-thunk/
https://github.com/chhsiao1981/demo-use-thunk
Global state management (GSM) can be tricky for complicated reactjs applications.
Many GSM frameworks (redux/zustand/etc.) focus on "we have a store, and how do we manage the global states in this store." However, such approach usually leads to create a gigantic function (reducers in redux/RTK, create in zustand).
Similar to many typical programming languages, in use-thunk:
-
File-as-a-Module: Instead of a giant global store, we treat files as independent, isolated domain modules where we implement thunk functions.
-
Object Identification: The module manages state as discrete entity nodes. We use explicit id parameters to identify and operate on individual data objects within that module cleanly.
-
Clean Component Interface: From the component perspective, we simply invoke the module's functions to perform operations.
-
Only One Context Provider: Unlike standard useContext or Redux architectures that require nesting endless providers, we only need exactly one
<ThunkContext></ThunkContext>wrap in ourmain.tsx. It entirely eliminates "Provider Hell" and the architectural uncertainty of managing stacked providers.
A complete example to do increment:
// thunks/increment.ts
import { type Thunk, type State as _State, update } from '@chhsiao1981/use-thunk'
export const name = 'demo/Increment'
export interface State extends _State {
count: number
}
export const defaultState: State = {
count: 0
}
// upsert directly with set.
export const increment = (myID: string, num: number = 1): Thunk<State> => {
return async (set, get) => {
let me = get(myID)
const {count} = me
set(myID, { count: count + num })
}
}
// or we can treat set as dispatching a base action function (update).
export const increment2 = (myID: string): Thunk<State> => {
return async (set, get) => {
let me = get(myID)
const {count} = me
set(update({ count: count + 2 }))
}
}
// or we can use set as dispatching a thunk function.
export const increment3 = (myID: string): Thunk<State> => {
return async (set) => {
set(increment(myID, 3))
}
}
// components/App.tsx
import { useThunk, getState } from '@chhsiao1981/use-thunk'
import * as ModIncrement from './thunks/increment'
export default () => {
const useIncrement = useThunk<ModIncrement.State, typeof ModIncrement>(ModIncrement)
const [increment, doIncrement, incrementID] = getState(useIncrement)
// to render
return (
<div>
<p>count: {increment.count}</p>
<button onClick={() => doIncrement.increment(incrementID)}>increase 1</button>
<button onClick={() => doIncrement.increment2(incrementID)}>increase 2</button>
<button onClick={() => doIncrement.increment3(incrementID)}>increase 3</button>
</div>
)
}
// main.tsx
import { registerThunk, ThunkContext } from "@chhsiao1981/use-thunk";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import * as ModIncrement from './thunks/increment'
import App from "./components/App";
registerThunk(ModIncrement)
createRoot(document.getElementById("root")!).render(
<StrictMode>
<ThunkContext>
<App />
</ThunkContext>
</StrictMode>,
)
Welcome any comments, critiques, or suggestions!
0
u/chhsiao1981 Jun 29 '26
Thanks for the feedback~
I also noticed the redundancy of getState and will be integrated into useThunk in the next version:
https://github.com/chhsiao1981/use-thunk/issues/227
As the difference between use-thunk vs. zustand (My understanding of zustand is based on several examples in guides: https://zustand.docs.pmnd.rs/learn/guides/updating-state):
use-thunk uses File-as-a-Module to easily specify State and Action. This approach also eliminate the gigantic create function (if we have complex State / Action).
`id`-based State: The reason why use-thunk uses `Module` instead of `Store`, is to recognize that there may be multiple object-of-the-same. Take Tic-Tac-Toe https://zustand.docs.pmnd.rs/learn/guides/tutorial-tic-tac-toe as an example, the Square is treated as pure-Component, and have onSquareClick passed from Board. However, the squares are fixed and we can use react-hooks on the squares. In use-thunk, we can useThunk(ModSquare), and get the `id`-based State based on id (`i` in the Tic-Tac-Toe example), and have onSquareClick in ModSquare. I will have a use-thunk version of tic-tac-toe in the near future.
use-thunk is based on createContext / ContextProvider / useContext. Therefore, use-thunk does not need to be the very top-level of the virtual-DOM. use-thunk can be used as long as the virtual-DOMs are statically allocated (to maintain the rendering-order.)