← Back to all hooks

useGlobalState

Build your own lightweight state store with Context + Reducer. A simple alternative to Redux or Zustand.

Live Demo

Counter Component 1

0

Counter Component 2

0

💡 How It Works:

  • Both components share the same global state
  • Changes in one component update the other
  • Built with Context + Reducer
  • Lightweight alternative to Redux

Code Examples

Basic Usage

import { createGlobalState } from "@uiblock/hooks";

const { Provider, useGlobalState } = createGlobalState(
  (state, action) => {
    // reducer logic
  },
  initialState
);

function App() {
  return (
    <Provider>
      <Component1 />
      <Component2 />
    </Provider>
  );
}

function Component1() {
  const [state, dispatch] = useGlobalState();
  // Use state and dispatch
}

Counter Store

import { createGlobalState } from "@uiblock/hooks";

// Create a counter store
type CounterState = { count: number };
type CounterAction = 
  | { type: 'INCREMENT' }
  | { type: 'DECREMENT' }
  | { type: 'RESET' };

const counterReducer = (state: CounterState, action: CounterAction): CounterState => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    case 'RESET':
      return { count: 0 };
    default:
      return state;
  }
};

const { Provider, useGlobalState } = createGlobalState(
  counterReducer,
  { count: 0 }
);

// Use in any component
function Counter() {
  const [state, dispatch] = useGlobalState();
  
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
    </div>
  );
}

With Selectors for Performance

import { createGlobalState } from "@uiblock/hooks";

const { Provider, useGlobalState } = createGlobalState(
  (state, action) => {
    switch (action.type) {
      case 'SET_USER':
        return { ...state, user: action.payload };
      case 'SET_THEME':
        return { ...state, theme: action.payload };
      default:
        return state;
    }
  },
  { user: null, theme: 'light' }
);

function UserDisplay() {
  // Only re-renders when user changes
  const [user, dispatch] = useGlobalState(state => state.user);
  return <div>{user?.name}</div>;
}

function ThemeToggle() {
  // Only re-renders when theme changes
  const [theme, dispatch] = useGlobalState(state => state.theme);
  
  return (
    <button onClick={() => 
      dispatch({ 
        type: 'SET_THEME', 
        payload: theme === 'light' ? 'dark' : 'light' 
      })
    }>
      Toggle Theme
    </button>
  );
}

Todo List Store

import { createGlobalState } from "@uiblock/hooks";

interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

type TodoState = { todos: Todo[] };
type TodoAction =
  | { type: 'ADD_TODO'; payload: string }
  | { type: 'TOGGLE_TODO'; payload: number }
  | { type: 'DELETE_TODO'; payload: number };

const todoReducer = (state: TodoState, action: TodoAction): TodoState => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        todos: [
          ...state.todos,
          { id: Date.now(), text: action.payload, completed: false }
        ]
      };
    case 'TOGGLE_TODO':
      return {
        todos: state.todos.map(todo =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
    case 'DELETE_TODO':
      return {
        todos: state.todos.filter(todo => todo.id !== action.payload)
      };
    default:
      return state;
  }
};

const { Provider, useGlobalState } = createGlobalState(
  todoReducer,
  { todos: [] }
);

function TodoList() {
  const [state, dispatch] = useGlobalState();
  
  return (
    <ul>
      {state.todos.map(todo => (
        <li key={todo.id}>
          <input
            type="checkbox"
            checked={todo.completed}
            onChange={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
          />
          {todo.text}
          <button onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}>
            Delete
          </button>
        </li>
      ))}
    </ul>
  );
}

function AddTodo() {
  const [text, setText] = useState('');
  const [, dispatch] = useGlobalState();
  
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (text.trim()) {
      dispatch({ type: 'ADD_TODO', payload: text });
      setText('');
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <button type="submit">Add</button>
    </form>
  );
}

Authentication Store

import { createGlobalState } from "@uiblock/hooks";

interface User {
  id: string;
  name: string;
  email: string;
}

type AuthState = {
  user: User | null;
  isAuthenticated: boolean;
  isLoading: boolean;
};

type AuthAction =
  | { type: 'LOGIN_START' }
  | { type: 'LOGIN_SUCCESS'; payload: User }
  | { type: 'LOGIN_ERROR' }
  | { type: 'LOGOUT' };

const authReducer = (state: AuthState, action: AuthAction): AuthState => {
  switch (action.type) {
    case 'LOGIN_START':
      return { ...state, isLoading: true };
    case 'LOGIN_SUCCESS':
      return {
        user: action.payload,
        isAuthenticated: true,
        isLoading: false
      };
    case 'LOGIN_ERROR':
      return { user: null, isAuthenticated: false, isLoading: false };
    case 'LOGOUT':
      return { user: null, isAuthenticated: false, isLoading: false };
    default:
      return state;
  }
};

const { Provider: AuthProvider, useGlobalState: useAuth } = createGlobalState(
  authReducer,
  { user: null, isAuthenticated: false, isLoading: false }
);

// Use in components
function LoginButton() {
  const [, dispatch] = useAuth();
  
  const handleLogin = async () => {
    dispatch({ type: 'LOGIN_START' });
    try {
      const user = await loginAPI();
      dispatch({ type: 'LOGIN_SUCCESS', payload: user });
    } catch (error) {
      dispatch({ type: 'LOGIN_ERROR' });
    }
  };
  
  return <button onClick={handleLogin}>Login</button>;
}

function UserProfile() {
  const [state] = useAuth();
  
  if (!state.isAuthenticated) {
    return <div>Please login</div>;
  }
  
  return <div>Welcome, {state.user?.name}!</div>;
}

How It Works

Here's the implementation:

import { createContext, useContext, useReducer, useCallback, ReactNode, createElement } from 'react'

export function createGlobalState<S, A = any>(
  reducer: (state: S, action: A) => S,
  initialState: S
) {
  const listeners = new Set<(state: S) => void>()
  let state = initialState

  const StateContext = createContext<{
    state: S
    dispatch: (action: A) => void
  } | null>(null)

  function Provider({ children }: { children: ReactNode }) {
    const [localState, localDispatch] = useReducer(reducer, initialState)

    const dispatch = useCallback((action: A) => {
      state = reducer(state, action)
      listeners.forEach(listener => listener(state))
      localDispatch(action)
    }, [])

    return createElement(
      StateContext.Provider,
      { value: { state: localState, dispatch } },
      children
    )
  }

  function useGlobalState(): [S, (action: A) => void]
  function useGlobalState<R>(selector: (state: S) => R): [R, (action: A) => void]
  function useGlobalState<R>(selector?: (state: S) => R) {
    const context = useContext(StateContext)
    if (!context) {
      throw new Error('useGlobalState must be used within Provider')
    }

    const { state, dispatch } = context
    const selectedState = selector ? selector(state) : state

    return [selectedState, dispatch]
  }

  return {
    Provider,
    useGlobalState,
    getState: () => state,
    subscribe: (listener: (state: S) => void) => {
      listeners.add(listener)
      return () => listeners.delete(listener)
    }
  }
}

💡 Use Cases

  • Lightweight global state management
  • Alternative to Redux for simple apps
  • Shared state across components
  • Theme management
  • User authentication state