useGlobalState
Build your own lightweight state store with Context + Reducer. A simple alternative to Redux or Zustand.
Live Demo
Counter Component 1
0
Counter Component 2
0
💡 How It Works:
- Both components share the same global state
- Changes in one component update the other
- Built with Context + Reducer
- Lightweight alternative to Redux
Code Examples
Basic Usage
import { createGlobalState } from "@uiblock/hooks";
const { Provider, useGlobalState } = createGlobalState(
(state, action) => {
// reducer logic
},
initialState
);
function App() {
return (
<Provider>
<Component1 />
<Component2 />
</Provider>
);
}
function Component1() {
const [state, dispatch] = useGlobalState();
// Use state and dispatch
}Counter Store
import { createGlobalState } from "@uiblock/hooks";
// Create a counter store
type CounterState = { count: number };
type CounterAction =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'RESET' };
const counterReducer = (state: CounterState, action: CounterAction): CounterState => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
case 'RESET':
return { count: 0 };
default:
return state;
}
};
const { Provider, useGlobalState } = createGlobalState(
counterReducer,
{ count: 0 }
);
// Use in any component
function Counter() {
const [state, dispatch] = useGlobalState();
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
<button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
</div>
);
}With Selectors for Performance
import { createGlobalState } from "@uiblock/hooks";
const { Provider, useGlobalState } = createGlobalState(
(state, action) => {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload };
case 'SET_THEME':
return { ...state, theme: action.payload };
default:
return state;
}
},
{ user: null, theme: 'light' }
);
function UserDisplay() {
// Only re-renders when user changes
const [user, dispatch] = useGlobalState(state => state.user);
return <div>{user?.name}</div>;
}
function ThemeToggle() {
// Only re-renders when theme changes
const [theme, dispatch] = useGlobalState(state => state.theme);
return (
<button onClick={() =>
dispatch({
type: 'SET_THEME',
payload: theme === 'light' ? 'dark' : 'light'
})
}>
Toggle Theme
</button>
);
}Todo List Store
import { createGlobalState } from "@uiblock/hooks";
interface Todo {
id: number;
text: string;
completed: boolean;
}
type TodoState = { todos: Todo[] };
type TodoAction =
| { type: 'ADD_TODO'; payload: string }
| { type: 'TOGGLE_TODO'; payload: number }
| { type: 'DELETE_TODO'; payload: number };
const todoReducer = (state: TodoState, action: TodoAction): TodoState => {
switch (action.type) {
case 'ADD_TODO':
return {
todos: [
...state.todos,
{ id: Date.now(), text: action.payload, completed: false }
]
};
case 'TOGGLE_TODO':
return {
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case 'DELETE_TODO':
return {
todos: state.todos.filter(todo => todo.id !== action.payload)
};
default:
return state;
}
};
const { Provider, useGlobalState } = createGlobalState(
todoReducer,
{ todos: [] }
);
function TodoList() {
const [state, dispatch] = useGlobalState();
return (
<ul>
{state.todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
/>
{todo.text}
<button onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}>
Delete
</button>
</li>
))}
</ul>
);
}
function AddTodo() {
const [text, setText] = useState('');
const [, dispatch] = useGlobalState();
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (text.trim()) {
dispatch({ type: 'ADD_TODO', payload: text });
setText('');
}
};
return (
<form onSubmit={handleSubmit}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button type="submit">Add</button>
</form>
);
}Authentication Store
import { createGlobalState } from "@uiblock/hooks";
interface User {
id: string;
name: string;
email: string;
}
type AuthState = {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
};
type AuthAction =
| { type: 'LOGIN_START' }
| { type: 'LOGIN_SUCCESS'; payload: User }
| { type: 'LOGIN_ERROR' }
| { type: 'LOGOUT' };
const authReducer = (state: AuthState, action: AuthAction): AuthState => {
switch (action.type) {
case 'LOGIN_START':
return { ...state, isLoading: true };
case 'LOGIN_SUCCESS':
return {
user: action.payload,
isAuthenticated: true,
isLoading: false
};
case 'LOGIN_ERROR':
return { user: null, isAuthenticated: false, isLoading: false };
case 'LOGOUT':
return { user: null, isAuthenticated: false, isLoading: false };
default:
return state;
}
};
const { Provider: AuthProvider, useGlobalState: useAuth } = createGlobalState(
authReducer,
{ user: null, isAuthenticated: false, isLoading: false }
);
// Use in components
function LoginButton() {
const [, dispatch] = useAuth();
const handleLogin = async () => {
dispatch({ type: 'LOGIN_START' });
try {
const user = await loginAPI();
dispatch({ type: 'LOGIN_SUCCESS', payload: user });
} catch (error) {
dispatch({ type: 'LOGIN_ERROR' });
}
};
return <button onClick={handleLogin}>Login</button>;
}
function UserProfile() {
const [state] = useAuth();
if (!state.isAuthenticated) {
return <div>Please login</div>;
}
return <div>Welcome, {state.user?.name}!</div>;
}How It Works
Here's the implementation:
import { createContext, useContext, useReducer, useCallback, ReactNode, createElement } from 'react'
export function createGlobalState<S, A = any>(
reducer: (state: S, action: A) => S,
initialState: S
) {
const listeners = new Set<(state: S) => void>()
let state = initialState
const StateContext = createContext<{
state: S
dispatch: (action: A) => void
} | null>(null)
function Provider({ children }: { children: ReactNode }) {
const [localState, localDispatch] = useReducer(reducer, initialState)
const dispatch = useCallback((action: A) => {
state = reducer(state, action)
listeners.forEach(listener => listener(state))
localDispatch(action)
}, [])
return createElement(
StateContext.Provider,
{ value: { state: localState, dispatch } },
children
)
}
function useGlobalState(): [S, (action: A) => void]
function useGlobalState<R>(selector: (state: S) => R): [R, (action: A) => void]
function useGlobalState<R>(selector?: (state: S) => R) {
const context = useContext(StateContext)
if (!context) {
throw new Error('useGlobalState must be used within Provider')
}
const { state, dispatch } = context
const selectedState = selector ? selector(state) : state
return [selectedState, dispatch]
}
return {
Provider,
useGlobalState,
getState: () => state,
subscribe: (listener: (state: S) => void) => {
listeners.add(listener)
return () => listeners.delete(listener)
}
}
}💡 Use Cases
- Lightweight global state management
- Alternative to Redux for simple apps
- Shared state across components
- Theme management
- User authentication state