DEV Community

Oleksandr Demian
Oleksandr Demian

Posted on

⚛️ Build a Simple Todo App with React Store - a Tiny React State Manager

State management in React can sometimes feel like overkill. Whether you're reaching for Redux, Zustand, or Jotai, you might be introducing complexity when all you need is a minimal way to share state globally across components.

That’s where @odemian/react-store shines — a tiny, typed, selector-based, and dependency-free global state library powered by useSyncExternalStore.

In this article, we’ll walk through how to build a clean, performant Todo App using @odemian/react-store in just a few minutes.


🔧 Installation

npm install @odemian/react-store
Enter fullscreen mode Exit fullscreen mode

🏗️ Step 1: Set up the Todo Store

Create a new file called stores/todoStore.ts:

import { createStore } from "@odemian/react-store";

export type TTodo = {
  id: number;
  name: string;
  done: boolean;
};

// Initialize the store with one task
export const [useTodos, updateTodos] = createStore<TTodo[]>([
  { id: Date.now(), name: "First task", done: false },
]);

// Add a new todo
export const addTodo = (name: string) =>
  updateTodos((todos) => [
    ...todos,
    { id: Date.now(), name, done: false },
  ]);

// Toggle todo status
export const toggleTodo = (id: number) =>
  updateTodos((todos) =>
    todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t))
  );

// Remove a todo
export const removeTodo = (id: number) =>
  updateTodos((todos) => todos.filter((t) => t.id !== id));
Enter fullscreen mode Exit fullscreen mode

🧑‍🎨 Step 2: Create Todo Input Component

import { useState } from "react";
import { addTodo } from "./stores/todoStore";

const CreateTodo = () => {
  const [text, setText] = useState("");

  const onAdd = () => {
    if (!text.trim()) return;
    addTodo(text);
    setText("");
  };

  return (
    <div>
      <input
        value={text}
        onChange={(e) => setText(e.currentTarget.value)}
        onKeyDown={(e) => e.key === "Enter" && onAdd()}
        placeholder="Add new task..."
      />
      <button onClick={onAdd}>Add</button>
    </div>
  );
};

export default CreateTodo;
Enter fullscreen mode Exit fullscreen mode

📋 Step 3: Display and Interact with Todos

import { useTodos, toggleTodo, removeTodo } from "./stores/todoStore";

const TodoList = () => {
  const todos = useTodos(); // Full state read

  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>
          <label>
            <input
              type="checkbox"
              checked={todo.done}
              onChange={() => toggleTodo(todo.id)}
            />
            {todo.name}
          </label>
          <button onClick={() => removeTodo(todo.id)}></button>
        </li>
      ))}
    </ul>
  );
};

export default TodoList;
Enter fullscreen mode Exit fullscreen mode

🧩 Step 4: Combine Components in App

import CreateTodo from "./CreateTodo";
import TodoList from "./TodoList";

const App = () => (
  <main>
    <h1>📝 Todo App</h1>
    <CreateTodo />
    <TodoList />
  </main>
);

export default App;
Enter fullscreen mode Exit fullscreen mode

✅ Why @odemian/react-store?

  • Tiny
  • Fast: Uses useSyncExternalStore for optimal React updates
  • Typed: Works seamlessly with TypeScript
  • Selector-based: Components only re-render when selected state changes
  • No boilerplate: No Provider, context, or hook destructurings in each component

🧾 Summary

With @odemian/react-store, global state is no longer complex. You get the power of selector-based subscriptions, type safety, and zero dependencies — all in a micro-package perfect for small to medium React apps.

If you’re tired of bloated state management tools and just want something clean, predictable, and fast, give it a try! 🚀


💬 Have questions or want to see more patterns with react-store? Drop them in the comments!

Top comments (0)