npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-hooks-global-states

v15.0.16

Published

A package to easily handle global state across your React components using hooks.

Readme

react-hooks-global-states 🌟

Image John Avatar

Zero setup. Zero complexity. Maximum performance. 🚀

One line of code. Infinite possibilities.

npm version Downloads License

Live DemoVideo TutorialCodePen


🎯 The One-Liner

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>;
}

🚀 Why Developers Love This Library

🎓 Zero Learning Curve

// If you know this...
const [state, setState] = useState(0);

// You know this!
const [state, setState] = useGlobalState();

Blazing Fast

Only components that care about a slice re-render. Surgical precision, maximum performance.

// Only re-renders when name changes
const [name] = useStore((s) => s.user.name);

🔗 Chainable Selectors

const useUsers = store.createSelectorHook((s) => s.users);
const useAdmins = useUsers.createSelectorHook((users) => users.filter((u) => u.isAdmin));

🎭 Actions (Optional)

const useAuth = createGlobalState(null, {
  actions: {
    login(credentials) {
      return async ({ setState }) => {
        const user = await api.login(credentials);
        setState(user);
      };
    },
  },
});

🎪 Context Mode

const Form = createContext({ name: '', email: '' });

<Form.Provider>
  <FormFields />
</Form.Provider>;

Non-Reactive API

Use state anywhere - even outside React components!

// In API interceptors, WebSockets, utils...
const token = useAuth.getState().token;
useAuth.setState({ user: newUser });

📦 Installation

npm install react-hooks-global-states

Platform-specific with built-in storage:

  • 🌐 Web: react-global-state-hooks (localStorage)
  • 📱 React Native: react-native-global-state-hooks (AsyncStorage by default, customizable, optional dependency)

🎬 Quick Start

30 Seconds to Global State

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>;
}

60 Seconds to Production-Ready

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>
  );
}

🌟 Core Features Deep Dive

1️⃣ Global State with createGlobalState

Create state that lives outside React's component tree. Perfect for app-wide state!

🎨 The Basics

// 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();
});

🎯 Surgical Re-renders with Selectors

The secret sauce: components only re-render when their specific slice changes!

const useStore = createGlobalState({
  user: { name: 'John', age: 30, email: '[email protected]' },
  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) |

⚡ Computed Values with Dependencies

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>;
}

🔗 Reusable Selector Hooks (The Game Changer!)

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 - When You Need Structure

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' }));
};

🔌 Non-Reactive API (Use Outside React!)

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);
  },
);

🔭 Observable Fragments (RxJS-style)

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);

📋 Metadata - Non-Reactive Side Info

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,
    });
  }
}

2️⃣ Scoped State with createContext

Sometimes you need state scoped to a component tree. That's what createContext is for!

🎪 The Basics

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>
  );
}

🎁 Provider Variations

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>
    </>
  );
}

🎯 Context + Selectors = ❤️

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>;
}

🎭 Context with Actions

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>
  );
}

🔗 Reusable Context Selectors

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>
  );
}

🧪 What About Testing?

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);
  });
});

🎬 Lifecycle Hooks

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>
  );
}

3️⃣ External Actions with actions

Extend any store with additional actions without modifying it! Perfect for separating concerns! 🎯

💪 Direct Binding

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

🎨 Action Templates (Define Before Ready!)

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 Calling Actions

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);

🎭 Access Store Actions

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);

🎪 Works with Context!

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>
  );
}

🔥 Advanced Patterns

🏗️ Production Architecture (File Organization)

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:

  • KISS - Keep It Simple, Stupid! Easy to navigate
  • Type-safe - Everything is strongly typed
  • Public/Private APIs - Internal actions don't pollute public interface
  • Namespace pattern - Everything bundled: todos$.useActiveTodos(), todos$.activeTodos$
  • Scalable - Easy to find, test, and maintain individual pieces

🎧 Smart Subscriptions

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 IDs

Generate branded unique identifiers with compile-time safety!

🏷️ Basic Usage

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"

🔒 Branded IDs (Type Safety!)

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!

🛡️ Runtime Validation

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!

🎯 Strict Branding

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!

💼 Real-World Example

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);

🎓 Learning Resources

| Resource | Description | | ------------------------------------------------------------------------------------ | --------------------------------- | | 🎮 Live Demo | Interactive examples | | 🎥 Video Tutorial | Full walkthrough | | 💻 CodePen | Try it online | | 📚 400+ Tests | Check the test suite for patterns |


🌐 Platform-Specific Versions

| 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 |


🎉 Why Developers Choose This

"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

The Bottom Line

| 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 |


🚀 Get Started Now

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. 🎉


Built with ❤️ for developers who value simplicity

⭐ Star on GitHub📝 Report Issues💬 Discussions