New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details โ†’ โ†’
Socket
Book a DemoSign in
Socket

react-hooks-global-states

Package Overview
Dependencies
Maintainers
1
Versions
123
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-hooks-global-states

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

latest
Source
npmnpm
Version
15.0.16
Version published
Weekly downloads
508
-21.97%
Maintainers
1
Weekly downloads
ย 
Created
Source

react-hooks-global-states ๐ŸŒŸ

Image John Avatar

Zero setup. Zero complexity. Maximum performance. ๐Ÿš€

One line of code. Infinite possibilities. โœจ

npm version Downloads License

Live Demo โ€ข Video Tutorial โ€ข CodePen

๐ŸŽฏ 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: '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:

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

FeatureBenefit
๐ŸŽฏ PrecisionEach hook subscribes to only what it needs
๐Ÿ”— ComposabilityBuild complex selectors from simple ones
โ™ป๏ธ ReusabilityDefine once, use everywhere
๐ŸŽจ Clean CodeNo repetitive selector logic in components
โšก PerformanceAutomatic 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

ResourceDescription
๐ŸŽฎ Live DemoInteractive examples
๐ŸŽฅ Video TutorialFull walkthrough
๐Ÿ’ป CodePenTry it online
๐Ÿ“š 400+ TestsCheck the test suite for patterns

๐ŸŒ Platform-Specific Versions

PackagePlatformSpecial Feature
react-hooks-global-statesReact / React NativeCore library
react-global-state-hooksWeblocalStorage integration
react-native-global-state-hooksReact NativeAsyncStorage 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 GetWhat 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

Keywords

preact

FAQs

Package last updated on 31 Jan 2026

Did you know?

Socket

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.

Install

Related posts