
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
react-hooks-global-states
Advanced tools
A package to easily handle global state across your React components using hooks.
![]()
Zero setup. Zero complexity. Maximum performance. ๐
One line of code. Infinite possibilities. โจ
Live Demo โข Video Tutorial โข CodePen
import { createGlobalState } from 'react-hooks-global-states';
export const useCounter = createGlobalState(0);
That's it. No providers. No context boilerplate. No configuration files. Just pure, beautiful state management. ๐จ
// Use it anywhere, instantly
function Counter() {
const [count, setCount] = useCounter();
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
๐ Zero Learning Curve
|
โก Blazing FastOnly components that care about a slice re-render. Surgical precision, maximum performance.
|
๐ Chainable Selectors
|
๐ญ Actions (Optional)
|
๐ช Context Mode
|
๏ฟฝ Non-Reactive APIUse state anywhere - even outside React components!
|
npm install react-hooks-global-states
Platform-specific with built-in storage:
react-global-state-hooks (localStorage)react-native-global-state-hooks (AsyncStorage by default, customizable, optional dependency)import { createGlobalState } from 'react-hooks-global-states';
// 1. Create it (anywhere)
const useTheme = createGlobalState('dark');
// 2. Use it (everywhere)
function ThemeToggle() {
const [theme, setTheme] = useTheme();
return <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>{theme} mode</button>;
}
function ThemedComponent() {
const [theme] = useTheme();
return <div className={theme}>Themed content</div>;
}
import { createGlobalState } from 'react-hooks-global-states';
const useAuth = createGlobalState(
{ user: null, token: null },
{
// Non-reactive metadata
metadata: { isLoading: false },
// Type-safe actions
actions: {
login(email, password) {
return async ({ setState, setMetadata }) => {
setMetadata({ isLoading: true });
try {
const { user, token } = await api.login(email, password);
setState({ user, token });
return { success: true };
} catch (error) {
return { success: false, error };
} finally {
setMetadata({ isLoading: false });
}
};
},
logout() {
return ({ setState }) => {
setState({ user: null, token: null });
};
},
},
},
);
// Usage
function LoginForm() {
const [auth, actions] = useAuth();
const { isLoading } = useAuth.getMetadata();
const handleSubmit = async (e) => {
e.preventDefault();
const result = await actions.login(email, password);
if (!result.success) toast.error(result.error);
};
return (
<form onSubmit={handleSubmit}>
{/* ... */}
<button disabled={isLoading}>{isLoading ? 'Logging in...' : 'Login'}</button>
</form>
);
}
createGlobalStateCreate state that lives outside React's component tree. Perfect for app-wide state!
// Primitives
const useCount = createGlobalState(0);
const useTheme = createGlobalState('light');
const useIsOpen = createGlobalState(false);
// Objects
const useUser = createGlobalState({ name: 'Guest', role: 'viewer' });
// Arrays
const useTodos = createGlobalState([
{ id: 1, text: 'Learn this library', done: true },
{ id: 2, text: 'Build something awesome', done: false },
]);
// With function initialization (useful for testing - allows store.reset())
const useExpensiveState = createGlobalState(() => {
return computeExpensiveInitialValue();
});
The secret sauce: components only re-render when their specific slice changes!
const useStore = createGlobalState({
user: { name: 'John', age: 30, email: 'john@example.com' },
theme: 'dark',
notifications: [],
settings: { sound: true, vibrate: false },
});
// This component ONLY re-renders when user.name changes
function UserName() {
const [name] = useStore((state) => state.user.name);
return <h1>{name}</h1>;
}
// Alternative: use.select() when you only need the value (not setState)
function UserNameAlt() {
const name = useStore.select((state) => state.user.name);
return <h1>{name}</h1>;
}
// This ONLY re-renders when theme changes
function ThemeSwitcher() {
const [theme, setStore] = useStore((state) => state.theme);
const toggleTheme = () => {
setStore((s) => ({ ...s, theme: theme === 'dark' ? 'light' : 'dark' }));
};
return <button onClick={toggleTheme}>{theme}</button>;
}
// This ONLY re-renders when notifications array changes
function NotificationCount() {
const [notifications] = useStore((state) => state.notifications);
return <span>{notifications.length}</span>;
}
Performance comparison:
| Approach | Re-renders when ANY state changes? |
|---|---|
| Context (naive) | โ YES (performance killer) |
| This library | โ NO (only selected slices) |
Derive values efficiently with automatic recomputation control! - selectors can depend on external state (like useState)!
const useStore = createGlobalState({
todos: [
{ id: 1, text: 'Task 1', completed: false, priority: 'high' },
{ id: 2, text: 'Task 2', completed: true, priority: 'low' },
{ id: 3, text: 'Task 3', completed: false, priority: 'high' },
],
});
function FilteredTodoList() {
// These are regular useState - NOT in the global store!
const [filter, setFilter] = useState('all'); // 'all' | 'active' | 'completed'
const [priorityFilter, setPriorityFilter] = useState('all'); // 'all' | 'high' | 'low'
// ๐ฅ The magic: selector recomputes when EITHER store OR external state changes!
const [filteredTodos] = useStore(
(state) => {
let todos = state.todos;
// Filter by completion
if (filter === 'active') todos = todos.filter((t) => !t.completed);
if (filter === 'completed') todos = todos.filter((t) => t.completed);
// Filter by priority
if (priorityFilter !== 'all') {
todos = todos.filter((t) => t.priority === priorityFilter);
}
return todos;
},
[filter, priorityFilter], // Recompute when these external dependencies change!
);
return (
<div>
<select value={filter} onChange={(e) => setFilter(e.target.value)}>
<option value="all">All</option>
<option value="active">Active</option>
<option value="completed">Completed</option>
</select>
<select value={priorityFilter} onChange={(e) => setPriorityFilter(e.target.value)}>
<option value="all">All Priorities</option>
<option value="high">High</option>
<option value="low">Low</option>
</select>
<ul>
{filteredTodos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</div>
);
}
// Advanced: use config object with custom equality
function AdvancedFiltered() {
const [filter, setFilter] = useState('all');
const [filteredTodos] = useStore(
(state) =>
state.todos.filter((t) => (filter === 'all' ? true : t.completed === (filter === 'completed'))),
{
dependencies: [filter],
// Custom equality - only recompute if todos array changed
isEqualRoot: (prev, next) => prev.todos === next.todos,
},
);
return <ul>{/* ... */}</ul>;
}
Create hooks from hooks! Chain them! Compose them! This is where it gets fun! ๐
const useStore = createGlobalState({
users: [
{ id: 1, name: 'Alice', role: 'admin', active: true },
{ id: 2, name: 'Bob', role: 'user', active: true },
{ id: 3, name: 'Charlie', role: 'admin', active: false },
],
currentUserId: 1,
});
// Create a reusable hook for users array
const useUsers = useStore.createSelectorHook((state) => state.users);
// Chain it! Create a hook for active users only
const useActiveUsers = useUsers.createSelectorHook((users) => users.filter((u) => u.active));
// Chain it again! Create a hook for active admins
const useActiveAdmins = useActiveUsers.createSelectorHook((users) => users.filter((u) => u.role === 'admin'));
// Create a hook for the current user
const useCurrentUser = useStore.createSelectorHook((state) => {
return state.users.find((u) => u.id === state.currentUserId);
});
// Now use them anywhere!
function UserStats() {
const [totalUsers] = useUsers();
const [activeUsers] = useActiveUsers();
const [activeAdmins] = useActiveAdmins();
return (
<div>
<p>Total: {totalUsers.length}</p>
<p>Active: {activeUsers.length}</p>
<p>Active Admins: {activeAdmins.length}</p>
</div>
);
}
function CurrentUserProfile() {
const [user] = useCurrentUser();
return <div>{user?.name}</div>;
}
// ๐ฏ Key insight: All these hooks share the SAME setState!
function AnyComponent() {
const [, setFromUsers] = useUsers();
const [, setFromAdmins] = useActiveAdmins();
const [, setFromStore] = useStore();
console.log(setFromUsers === setFromAdmins); // true!
console.log(setFromUsers === setFromStore); // true!
console.log(setFromUsers === useStore.setState); // true!
}
Why this is powerful:
| Feature | Benefit |
|---|---|
| ๐ฏ Precision | Each hook subscribes to only what it needs |
| ๐ Composability | Build complex selectors from simple ones |
| โป๏ธ Reusability | Define once, use everywhere |
| ๐จ Clean Code | No repetitive selector logic in components |
| โก Performance | Automatic memoization and change detection |
Actions aren't required, but they're awesome for organizing mutations!
const useStore = createGlobalState(
{
todos: new Map(), // Map<id, todo>
filter: 'all',
},
{
actions: {
addTodo(text) {
return ({ setState, getState }) => {
const id = Date.now();
const newTodo = { id, text, completed: false };
setState((s) => ({
...s,
todos: new Map(s.todos).set(id, newTodo),
}));
};
},
toggleTodo(id) {
return ({ setState, getState }) => {
const { todos } = getState();
const todo = todos.get(id);
if (!todo) return;
setState((s) => ({
...s,
todos: new Map(s.todos).set(id, { ...todo, completed: !todo.completed }),
}));
};
},
clearCompleted() {
return ({ setState, getState }) => {
const { todos } = getState();
// Filter out completed todos, create new Map
const newTodos = new Map();
todos.forEach((todo, id) => {
if (!todo.completed) newTodos.set(id, todo);
});
setState((s) => ({ ...s, todos: newTodos }));
// Actions can call other actions!
this.updateStats();
};
},
updateStats() {
return ({ getState }) => {
const { todos } = getState();
const completed = Array.from(todos.values()).filter((t) => t.completed).length;
console.log(`${completed} completed`);
};
},
// Async actions? No problem!
syncWithServer() {
return async ({ setState }) => {
const todosArray = await api.fetchTodos();
const todosMap = new Map(todosArray.map((t) => [t.id, t]));
setState((s) => ({ ...s, todos: todosMap }));
};
},
},
},
);
// When actions are defined, the second element is the actions object!
function TodoApp() {
const [state, actions] = useStore();
return (
<div>
<button onClick={() => actions.addTodo('New task')}>Add</button>
<button onClick={() => actions.clearCompleted()}>Clear</button>
<button onClick={() => actions.syncWithServer()}>Sync</button>
{Array.from(state.todos.values()).map((todo) => (
<div key={todo.id} onClick={() => actions.toggleTodo(todo.id)}>
{todo.text}
</div>
))}
</div>
);
}
// ๏ฟฝ Don't worry, you can still go rogue with setState!
const handleQuickFix = () => {
useStore.setState((s) => ({ ...s, filter: 'all' }));
};
Access your state from anywhere - even outside components!
const useAuth = createGlobalState({ user: null, token: null });
// โจ In a utility file
export async function loginUser(email, password) {
try {
const { user, token } = await api.login(email, password);
useAuth.setState({ user, token });
return { success: true };
} catch (error) {
return { success: false, error };
}
}
// โจ In an API interceptor
axios.interceptors.request.use((config) => {
const { token } = useAuth.getState();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// โจ In a WebSocket handler
socket.on('user-updated', (user) => {
useAuth.setState((state) => ({ ...state, user }));
});
// โจ Subscribe to changes (even outside React!)
const unsubscribe = useAuth.subscribe(
// Selector
(state) => state.user,
// Callback
function onChange(newUser) {
console.log('User changed:', user);
analytics.identify(user?.id);
},
);
Create observable slices of your state for reactive programming!
const useStore = createGlobalState({
count: 0,
user: { name: 'John' },
});
// Create an observable that tracks just the count
const countObservable = useStore.createObservable((state) => state.count);
// Use it outside React
countObservable.subscribe((count) => {
console.log('Count is now:', count);
if (count > 10) {
alert('Count is high!');
}
});
// Observables also have getState
console.log(countObservable.getState()); // Current count value
// Chain observables!
const doubledObservable = countObservable.createObservable((count) => count * 2);
Store data that doesn't need to trigger re-renders!
const useStore = createGlobalState(
{ items: [] },
{
metadata: {
isLoading: false,
lastFetch: null,
error: null,
retryCount: 0,
},
},
);
// Metadata changes don't trigger re-renders!
useStore.setMetadata({ isLoading: true });
// But you can access it anytime
const meta = useStore.getMetadata();
console.log(meta.isLoading); // true
// Perfect for loading states, error tracking, etc.
async function fetchData() {
useStore.setMetadata({ isLoading: true, error: null });
try {
const items = await api.fetch();
useStore.setState({ items });
useStore.setMetadata({
isLoading: false,
lastFetch: new Date(),
});
} catch (error) {
useStore.setMetadata({
isLoading: false,
error: error.message,
retryCount: useStore.getMetadata().retryCount + 1,
});
}
}
createContextSometimes you need state scoped to a component tree. That's what createContext is for!
import { createContext } from 'react-hooks-global-states';
// Create a context
const UserFormContext = createContext({
name: '',
email: '',
age: 0,
});
function App() {
return (
<UserFormContext.Provider>
<FormFields />
<FormPreview />
</UserFormContext.Provider>
);
}
function FormFields() {
const [form, setForm] = UserFormContext.use();
return (
<div>
<input value={form.name} onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))} />
<input value={form.email} onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))} />
</div>
);
}
function FormPreview() {
const [form] = UserFormContext.use();
return (
<div>
Hello {form.name} ({form.email})
</div>
);
}
const ThemeContext = createContext('light');
function App() {
return (
<>
{/* Default value */}
<ThemeContext.Provider>
<Page /> {/* Gets 'light' */}
</ThemeContext.Provider>
{/* Custom value */}
<ThemeContext.Provider value="dark">
<Page /> {/* Gets 'dark' */}
</ThemeContext.Provider>
{/* Derived from parent (yes, you can nest!) */}
<ThemeContext.Provider value={(parent) => (parent === 'dark' ? 'light' : 'dark')}>
<Page /> {/* Gets opposite of parent */}
</ThemeContext.Provider>
</>
);
}
Everything that works with createGlobalState works with createContext!
const FormContext = createContext({
personal: { name: '', age: 0 },
contact: { email: '', phone: '' },
preferences: { theme: 'light', notifications: true },
});
// Only re-renders when name changes!
function NameField() {
const [name, setForm] = FormContext.use((state) => state.personal.name);
return (
<input
value={name}
onChange={(e) => setForm((s) => ({ ...s, personal: { ...s.personal, name: e.target.value } }))}
/>
);
}
// Only re-renders when email changes!
function EmailField() {
const [email] = FormContext.use((state) => state.contact.email);
return <div>{email}</div>;
}
const CounterContext = createContext(0, {
actions: {
increment(amount = 1) {
return ({ setState, getState }) => {
setState(getState() + amount);
};
},
decrement(amount = 1) {
return ({ setState, getState }) => {
setState(getState() - amount);
};
},
reset() {
return ({ setState }) => setState(0);
},
},
});
function Counter() {
const [count, actions] = CounterContext.use();
return (
<div>
<h1>{count}</h1>
<button onClick={() => actions.increment()}>+</button>
<button onClick={() => actions.decrement()}>-</button>
<button onClick={() => actions.reset()}>Reset</button>
</div>
);
}
function App() {
return (
<CounterContext.Provider>
<Counter />
</CounterContext.Provider>
);
}
Yes, chainable selectors work here too!
const DataContext = createContext({
users: [
/* ... */
],
posts: [
/* ... */
],
filter: 'all',
});
// Create reusable hooks
const useUsers = DataContext.use.createSelectorHook((s) => s.users);
const useActiveUsers = useUsers.createSelectorHook((u) => u.filter((user) => user.active));
const usePosts = DataContext.use.createSelectorHook((s) => s.posts);
function App() {
return (
<DataContext.Provider>
<UserList />
<PostList />
</DataContext.Provider>
);
}
function UserList() {
const [activeUsers] = useActiveUsers();
return (
<ul>
{activeUsers.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}
Do you need to access the context to spy on state changes, inject test data, or manipulate state directly? No problem!
import { renderHook } from '@testing-library/react';
import CounterContext from './CounterContext'; // { count: 0 } with increment() action
describe('CounterContext', () => {
it('should manipulate state from tests', () => {
// Create wrapper with direct access to context
const { wrapper, context } = CounterContext.Provider.makeProviderWrapper();
// Render the hook
const { result } = renderHook(() => CounterContext.use(), { wrapper });
// Read initial state
expect(result.current[0].count).toBe(0);
// Call the action through the wrapper
context.current.actions.increment();
// Verify state updated
expect(result.current[0].count).toBe(1);
});
});
React to context lifecycle events!
const DataContext = createContext([], {
callbacks: {
onCreated: (store) => {
console.log('Context created with:', store.getState());
},
onMounted: (store) => {
console.log('Provider mounted!');
// Fetch data on mount
fetchData().then((data) => store.setState(data));
// Return cleanup
return () => {
console.log('Provider unmounting!');
};
},
},
});
// Or per-provider
function App() {
return (
<DataContext.Provider
onCreated={(store) => console.log('Created!')}
onMounted={(store) => {
console.log('Mounted!');
return () => console.log('Cleanup!');
}}
>
<Content />
</DataContext.Provider>
);
}
actionsExtend any store with additional actions without modifying it! Perfect for separating concerns! ๐ฏ
import { createGlobalState, actions } from 'react-hooks-global-states';
const useCounter = createGlobalState(0);
// Create actions for the store
const counterActions = actions(useCounter, {
increment(amount = 1) {
return ({ setState, getState }) => {
setState(getState() + amount);
};
},
decrement(amount = 1) {
return ({ setState, getState }) => {
setState(getState() - amount);
};
},
double() {
return ({ setState, getState }) => {
setState(getState() * 2);
};
},
});
// Use them anywhere!
counterActions.increment(5); // count = 5
counterActions.double(); // count = 10
counterActions.decrement(3); // count = 7
Need actions in contexts or lifecycle hooks (onInit) before the store API is available? Create action templates first, bind them later!
import { actions, InferAPI, createContext } from 'react-hooks-global-states';
type SessionAPI = InferAPI<typeof SessionContext>;
// Create internal actions template
const internalActions = actions<SessionAPI>()({
loadData() {
return async ({ setState }) => {
...
};
},
});
const SessionContext = createContext(
{ user: null, preferences: {}, lastSync: null },
{
callbacks: {
onInit: (api) => {
const { loadData } = internalActions(api);
// Load data on init
loadData();
// Sync every 5 minutes
const interval = setInterval(loadData, 5 * 60 * 1000);
// Cleanup on unmount
return () => clearInterval(interval);
},
},
// public actions
actions: {
logout() {
return (api) => {
const { setState } = api as SessionAPI;
setState({ user: null, preferences: {}, lastSync: null });
};
},
},
},
);
// Internal actions are not expose through the context
const { loadData } = SessionContext.use.actions();
console.log(loadData); // undefined
Actions can call each other in multiple ways!
const useStore = createGlobalState({ count: 0, history: [] }, {
actions: {
clearHistory() {
return ({ setState }) => {
...
};
},
},
});
const storeActions = actions(useStore, {
logAction(message) {
return ({ setState, getState }) => {
...
};
},
increment(amount = 1) {
return ({ setState, getState }) => {
setState((s) => ({ ...s, count: s.count + amount }));
// Call another action in this group with 'this'
this.logAction(`Incremented by ${amount}`);
};
},
incrementTwice(amount = 1) {
return ({ actions }) => {
// Call actions with 'this'
this.increment(amount);
this.increment(amount);
// Or directly from storeActions
storeActions.logAction(`Incremented twice by ${amount}`);
// Access store's public actions via 'actions' parameter
actions.clearHistory();
};
},
});
storeActions.incrementTwice(5);
External actions can call each other with this and access the store's public actions!
const useStore = createGlobalState(
{ count: 0, logs: [] },
{
actions: {
log(message) {
return ({ setState }) => {
...
};
},
},
},
);
const extraActions = actions(useStore, {
addToHistory(message) {
return ({ setState }) => {
...
};
},
incrementAndLog(amount = 1) {
return ({ setState, actions }) => {
setState((s) => ({ ...s, count: s.count + amount }));
// Call sibling actions with 'this'
this.addToHistory(`Incremented by ${amount}`);
// Access store's public actions
actions.log(`Count increased by ${amount}`);
};
},
});
extraActions.incrementAndLog(5);
import { actions, InferAPI } from 'react-hooks-global-states';
const CounterContext = createContext(0);
type CounterAPI = InferAPI<typeof CounterContext>;
// Define action template outside - reusable domain actions
const counterActionsTemplate = actions<CounterAPI>()({
increment() {
return ({ setState, getState }) => {
setState(getState() + 1);
};
},
decrement() {
return ({ setState, getState }) => {
setState(getState() - 1);
};
},
});
function App() {
return (
<CounterContext.Provider>
<Counter />
</CounterContext.Provider>
);
}
function Counter() {
const api = CounterContext.use.api();
// Bind template to context instance
const { increment, decrement } = useMemo(() => counterActionsTemplate(api), [api]);
return (
<div>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
</div>
);
}
Real-world large-scale applications need clean separation! Here's how to organize a store with actions in separate files, custom hooks, observables, and a namespace pattern:
File Structure:
src/stores/todos/
โโโ index.ts // Namespace - bundles everything
โโโ store.ts // Store definition
โโโ constants/ // Initial state & metadata
โ โโโ initialValue.ts
โ โโโ metadata.ts
โโโ types/ // Type definitions
โ โโโ TodosAPI.ts
โ โโโ Todo.ts
โโโ hooks/ // Custom selector hooks
โ โโโ useActiveTodos.ts
โ โโโ useCompletedTodos.ts
โโโ observables/ // Observable fragments
โ โโโ activeTodos$.ts
โโโ helpers/ // Utility functions
โ โโโ createTodo.ts
โโโ actions/
โโโ index.ts // Export all actions
โโโ addTodo.ts
โโโ toggleTodo.ts
โโโ internal/
โโโ syncWithServer.ts
Store Definition (store.ts):
import { createGlobalState, actions, type InferAPI } from 'react-hooks-global-states';
import { addTodo, toggleTodo, removeTodo } from './actions';
import { syncWithServer } from './actions/internal';
import { initialValue, metadata } from './constants';
type TodosAPI = InferAPI<typeof todosStore>;
// Internal actions template
const internalActions = actions<TodosAPI>()({
syncWithServer,
});
const todosStore = createGlobalState(initialValue, {
metadata,
// Public actions - exposed to consumers
actions: {
addTodo,
toggleTodo,
removeTodo,
},
callbacks: {
onInit: (api) => {
// Bind and extend with internal actions (not exposed publicly)
const { syncWithServer } = internalActions(api);
// Auto-sync every 30 seconds
const interval = setInterval(() => syncWithServer(), 30000);
return () => clearInterval(interval);
},
},
});
// Export types (defined in types/ folder)
export type { TodosAPI } from './types';
export default todosStore;
Type Files (types/TodosAPI.ts):
// types/TodosAPI.ts
// prevents circular type references
export type TodosAPI = import('../store').TodosAPI;
Action File (actions/addTodo.ts):
import type { TodosAPI } from '../types';
/**
* Adds a new todo and syncs with server
*/
function addTodo(this: TodosAPI['actions'], text: string) {
return async ({ setState }: TodosAPI): Promise<void> => {
const newTodo = {
id: crypto.randomUUID(),
text,
completed: false,
createdAt: new Date(),
};
setState((s) => ({
...s,
todos: [...s.todos, newTodo],
}));
// Call internal action to sync
await this.syncWithServer();
};
}
export default addTodo;
Custom Hook (hooks/useActiveTodos.ts):
import todosStore from '../store';
export const useActiveTodos = todosStore.createSelectorHook((state) =>
state.todos.filter((t) => !t.completed),
);
Observable (observables/activeTodos$.ts):
import { useActiveTodos } from '../hooks/useActiveTodos';
// Derive from hook - reuses the filter logic
export const activeTodos$ = useActiveTodos.createObservable((s) => s);
Namespace Pattern (index.ts):
import store from './store';
import { useActiveTodos, useCompletedTodos } from './hooks';
import { activeTodos$, completedTodos$ } from './observables';
// Bundle everything into a clean namespace
const todos$ = Object.assign(store, {
// Custom hooks
useActiveTodos,
useCompletedTodos,
// Observables
activeTodos$,
completedTodos$,
});
export default todos$;
Usage:
import todos$ from './stores/todos';
function TodoApp() {
// Use the namespace
const activeTodos = todos$.useActiveTodos();
// Subscribe to observable outside React
useEffect(() => {
const sub = todos$.activeTodos$.subscribe((todos) => {
console.log('Active todos changed:', todos.length);
});
return () => sub();
}, []);
return (
<div>
<button onClick={() => todos$.actions.addTodo('New task')}>Add</button>
{activeTodos.map((todo) => (
<div key={todo.id}>{todo.text}</div>
))}
</div>
);
}
Why this pattern rocks:
todos$.useActiveTodos(), todos$.activeTodos$Subscribe to specific slices outside React!
const useStore = createGlobalState({
user: { name: 'John', role: 'admin' },
theme: 'dark',
notifications: [],
});
// Subscribe to just the theme
const unsubTheme = useStore.subscribe(
(state) => state.theme,
(theme) => {
document.body.className = theme;
localStorage.setItem('theme', theme);
},
);
// Subscribe to user role changes
const unsubRole = useStore.subscribe(
(state) => state.user.role,
(role) => {
console.log('User role changed to:', role);
analytics.track('role_changed', { role });
},
);
// Cleanup
unsubTheme();
unsubRole();
uniqueId - Type-Safe Unique IDsGenerate branded unique identifiers with compile-time safety!
import { uniqueId } from 'react-hooks-global-states';
// Simple IDs
const id1 = uniqueId(); // "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
const id2 = uniqueId('user:'); // "user:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
const id3 = uniqueId('session:'); // "session:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
Create ID generators with compile-time type checking!
// Create branded generators
const generateUserId = uniqueId.for('user:');
const generatePostId = uniqueId.for('post:');
type UserId = ReturnType<typeof generateUserId>;
type PostId = ReturnType<typeof generatePostId>;
const userId: UserId = generateUserId(); // โ
"user:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
const postId: PostId = generatePostId(); // โ
"post:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
// TypeScript prevents mixing!
const wrong: UserId = generatePostId(); // โ Type error!
const generateUserId = uniqueId.for('user:');
const id = generateUserId(); // "user:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
// Check if a string is a valid user ID
if (generateUserId.is(id)) {
console.log('Valid user ID!');
}
generateUserId.is('user:a1b2c3d4-e5f6-7890-abcd-ef1234567890'); // โ
true
generateUserId.is('post:a1b2c3d4-e5f6-7890-abcd-ef1234567890'); // โ false
// Assert (throws if invalid)
generateUserId.assert('user:a1b2c3d4-e5f6-7890-abcd-ef1234567890'); // โ
OK
generateUserId.assert('post:a1b2c3d4-e5f6-7890-abcd-ef1234567890'); // โ Throws error!
Maximum type safety with symbol branding!
declare const UserBrand: unique symbol;
declare const PostBrand: unique symbol;
const generateUserId = uniqueId.for('user:').strict<typeof UserBrand>();
const generatePostId = uniqueId.for('post:').strict<typeof PostBrand>();
// Even with same prefix, types are incompatible!
const generateUserId2 = uniqueId.for('user:').strict<typeof PostBrand>();
type UserId = ReturnType<typeof generateUserId>;
type UserId2 = ReturnType<typeof generateUserId2>;
const id1: UserId = generateUserId(); // โ
const id2: UserId = generateUserId2(); // โ Different brands!
import { createGlobalState, uniqueId } from 'react-hooks-global-states';
// Create typed ID generators
const generateUserId = uniqueId.for('user:');
const generateTodoId = uniqueId.for('todo:');
type UserId = ReturnType<typeof generateUserId>;
type TodoId = ReturnType<typeof generateTodoId>;
interface User {
id: UserId;
name: string;
}
interface Todo {
id: TodoId;
text: string;
assignedTo: UserId | null;
}
const useApp = createGlobalState(
{
users: [] as User[],
todos: [] as Todo[],
},
{
actions: {
addUser(name: string) {
return ({ setState, getState }) => {
const user: User = {
id: generateUserId(), // Type-safe!
name,
};
setState((s) => ({
...s,
users: [...s.users, user],
}));
};
},
addTodo(text: string, assignedTo: UserId | null = null) {
return ({ setState, getState }) => {
const todo: Todo = {
id: generateTodoId(), // Type-safe!
text,
assignedTo,
};
setState((s) => ({
...s,
todos: [...s.todos, todo],
}));
};
},
assignTodo(todoId: TodoId, userId: UserId) {
return ({ setState, getState }) => {
// TypeScript ensures correct ID types!
setState((s) => ({
...s,
todos: s.todos.map((t) => (t.id === todoId ? { ...t, assignedTo: userId } : t)),
}));
};
},
},
},
);
// Usage
const [, actions] = useApp();
// Create a new user
const userId = generateUserId();
actions.addUser('John');
actions.addTodo('Build feature', userId);
| Resource | Description |
|---|---|
| ๐ฎ Live Demo | Interactive examples |
| ๐ฅ Video Tutorial | Full walkthrough |
| ๐ป CodePen | Try it online |
| ๐ 400+ Tests | Check the test suite for patterns |
| Package | Platform | Special Feature |
|---|---|---|
react-hooks-global-states | React / React Native | Core library |
react-global-state-hooks | Web | localStorage integration |
react-native-global-state-hooks | React Native | AsyncStorage integration |
"I replaced 500 lines of Redux with 50 lines of this. Mind blown." ๐คฏ
- Every developer who tries it
"The useState I always wanted." โค๏ธ
- React developers everywhere
"Finally, state management that doesn't fight me." ๐ฅ
- Tired developers worldwide
| What You Get | What You Don't |
|---|---|
โ
useState API | โ Boilerplate |
| โ Surgical re-renders | โ Whole-tree updates |
| โ Chainable selectors | โ Repetitive code |
| โ TypeScript inference | โ Manual typing |
| โ Global + Context | โ Either/or choice |
| โ Actions (optional) | โ Required structure |
| โ 30-second learning curve | โ Week-long training |
npm install react-hooks-global-states
Then in your app:
import { createGlobalState } from 'react-hooks-global-states';
const useTheme = createGlobalState('light');
function App() {
const [theme, setTheme] = useTheme();
return <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>Toggle Theme</button>;
}
That's it. You're done. ๐
โญ Star on GitHub โข ๐ Report Issues โข ๐ฌ Discussions
FAQs
A package to easily handle global state across your React components using hooks.
The npm package react-hooks-global-states receives a total of 458 weekly downloads. As such, react-hooks-global-states popularity was classified as not popular.
We found that react-hooks-global-states demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.ย It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.