🚀 We’re actively developing new and unique custom hooks for React! Contribute on GitHub

Quick Start Guide

Get up and running with light-hooks in 5 minutes. Transform your React components with powerful, lightweight hooks.

Ready to supercharge your React app? This guide will get you from zero to hero with light-hooks in just a few minutes!

Note:

This guide assumes you have a React project already set up. If you don't, create one with npx create-react-app my-app or npm create vite@latest my-app -- --template react.

Build a Complete Todo App

We'll build a fully functional todo app showcasing the most popular light-hooks. By the end, you'll have a real app and understand how to use:

  • useLocalStorage - for persistent data
  • useToggle - for boolean states
  • useCounter - for managing numbers
  • useDebounce - for search optimization

Step 1: Install and Setup

Install light-hooks in your project:

npm install light-hooks

Create a new component file TodoApp.jsx (or .tsx for TypeScript):

import React, { useState } from 'react';
import { 
  useLocalStorage, 
  useToggle, 
  useCounter, 
  useDebounce 
} from 'light-hooks';

export default function TodoApp() {
  return <div>Let's build something awesome!</div>;
}

Step 2: Persistent Todo Storage

Replace the basic useState with persistent storage:

function TodoApp() {
  // This replaces useState and automatically syncs with localStorage!
  const [todos, setTodos] = useLocalStorage('my-todos', []);
  const [newTodo, setNewTodo] = useState('');

  const addTodo = () => {
    if (newTodo.trim()) {
      setTodos([...todos, {
        id: Date.now(),
        text: newTodo,
        completed: false
      }]);
      setNewTodo('');
    }
  };

  return (
    <div style={{ padding: '20px', maxWidth: '500px', margin: '0 auto' }}>
      <h1>My Awesome Todo App</h1>
      
      <div style={{ marginBottom: '20px' }}>
        <input
          value={newTodo}
          onChange={(e) => setNewTodo(e.target.value)}
          placeholder="What needs to be done?"
          style={{ padding: '10px', marginRight: '10px', width: '300px' }}
        />
        <button onClick={addTodo} style={{ padding: '10px' }}>
          Add Todo
        </button>
      </div>

      <div>
        {todos.map(todo => (
          <div key={todo.id} style={{ padding: '10px', border: '1px solid #ddd', margin: '5px 0' }}>
            {todo.text}
          </div>
        ))}
      </div>
    </div>
  );
}

Note:

Amazing! Your todos now persist automatically. Refresh the page - they're still there!

Step 3: Add Toggle Functionality

Let's add the ability to mark todos as complete using useToggle:

function TodoItem({ todo, onToggle, onDelete }) {
  return (
    <div style={{ 
      padding: '10px', 
      border: '1px solid #ddd', 
      margin: '5px 0',
      display: 'flex',
      justifyContent: 'space-between',
      backgroundColor: todo.completed ? '#f0f8f0' : 'white'
    }}>
      <span 
        style={{ 
          textDecoration: todo.completed ? 'line-through' : 'none',
          color: todo.completed ? '#888' : 'black'
        }}
      >
        {todo.text}
      </span>
      <div>
        <button 
          onClick={() => onToggle(todo.id)}
          style={{ marginRight: '10px', padding: '5px 10px' }}
        >
          {todo.completed ? 'Undo' : 'Done'}
        </button>
        <button 
          onClick={() => onDelete(todo.id)}
          style={{ padding: '5px 10px', backgroundColor: '#ff4444', color: 'white' }}
        >
          Delete
        </button>
      </div>
    </div>
  );
}

function TodoApp() {
  const [todos, setTodos] = useLocalStorage('my-todos', []);
  const [newTodo, setNewTodo] = useState('');
  
  // Perfect for show/hide states!
  const [showCompleted, toggleShowCompleted] = useToggle(true);

  const addTodo = () => {
    if (newTodo.trim()) {
      setTodos([...todos, {
        id: Date.now(),
        text: newTodo,
        completed: false
      }]);
      setNewTodo('');
    }
  };

  const toggleTodo = (id) => {
    setTodos(todos.map(todo => 
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };

  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  const filteredTodos = showCompleted 
    ? todos 
    : todos.filter(todo => !todo.completed);

  return (
    <div style={{ padding: '20px', maxWidth: '500px', margin: '0 auto' }}>
      <h1>My Awesome Todo App</h1>
      
      <div style={{ marginBottom: '20px' }}>
        <input
          value={newTodo}
          onChange={(e) => setNewTodo(e.target.value)}
          placeholder="What needs to be done?"
          style={{ padding: '10px', marginRight: '10px', width: '300px' }}
        />
        <button onClick={addTodo} style={{ padding: '10px' }}>
          Add Todo
        </button>
      </div>

      <div style={{ marginBottom: '20px' }}>
        <button 
          onClick={toggleShowCompleted}
          style={{ padding: '10px' }}
        >
          {showCompleted ? 'Hide' : 'Show'} Completed
        </button>
      </div>

      <div>
        {filteredTodos.map(todo => (
          <TodoItem 
            key={todo.id} 
            todo={todo} 
            onToggle={toggleTodo}
            onDelete={deleteTodo}
          />
        ))}
      </div>
    </div>
  );
}

Step 4: Add Statistics with useCounter

Let's add some stats using useCounter:

function TodoStats({ todos }) {
  // useCounter provides increment, decrement, reset, and set methods!
  const [viewCount, { increment: incrementViews }] = useCounter(0);

  React.useEffect(() => {
    incrementViews(); // Count each time stats are viewed
  }, []);

  const completed = todos.filter(todo => todo.completed).length;
  const total = todos.length;
  const pending = total - completed;

  return (
    <div style={{ 
      padding: '15px', 
      backgroundColor: '#f5f5f5', 
      borderRadius: '8px',
      margin: '20px 0'
    }}>
      <h3>Statistics</h3>
      <p>Total: {total}</p>
      <p>Completed: {completed}</p>
      <p>Pending: {pending}</p>
      <p>Times viewed: {viewCount}</p>
    </div>
  );
}

// Add <TodoStats todos={todos} /> to your TodoApp component

Step 5: Add Smart Search with useDebounce

Finally, let's add a search feature that doesn't spam the filter function:

function TodoApp() {
  const [todos, setTodos] = useLocalStorage('my-todos', []);
  const [newTodo, setNewTodo] = useState('');
  const [showCompleted, toggleShowCompleted] = useToggle(true);
  
  // Search functionality
  const [searchTerm, setSearchTerm] = useState('');
  // Only searches after user stops typing for 300ms!
  const debouncedSearch = useDebounce(searchTerm, 300);

  // ... other functions ...

  const filteredTodos = todos
    .filter(todo => showCompleted || !todo.completed)
    .filter(todo => 
      debouncedSearch 
        ? todo.text.toLowerCase().includes(debouncedSearch.toLowerCase())
        : true
    );

  return (
    <div style={{ padding: '20px', maxWidth: '500px', margin: '0 auto' }}>
      <h1>My Awesome Todo App</h1>
      
      {/* Add Todo Section */}
      <div style={{ marginBottom: '20px' }}>
        <input
          value={newTodo}
          onChange={(e) => setNewTodo(e.target.value)}
          placeholder="What needs to be done?"
          style={{ padding: '10px', marginRight: '10px', width: '300px' }}
        />
        <button onClick={addTodo} style={{ padding: '10px' }}>
          Add Todo
        </button>
      </div>

      {/* Search Section */}
      <div style={{ marginBottom: '20px' }}>
        <input
          value={searchTerm}
          onChange={(e) => setSearchTerm(e.target.value)}
          placeholder="Search todos..."
          style={{ padding: '10px', width: '100%' }}
        />
      </div>

      {/* Controls */}
      <div style={{ marginBottom: '20px' }}>
        <button 
          onClick={toggleShowCompleted}
          style={{ padding: '10px' }}
        >
          {showCompleted ? 'Hide' : 'Show'} Completed
        </button>
      </div>

      {/* Stats */}
      <TodoStats todos={todos} />

      {/* Todo List */}
      <div>
        {filteredTodos.length === 0 ? (
          <p style={{ textAlign: 'center', color: '#888' }}>
            {debouncedSearch ? 'No todos match your search' : 'No todos yet. Add one above!'}
          </p>
        ) : (
          filteredTodos.map(todo => (
            <TodoItem 
              key={todo.id} 
              todo={todo} 
              onToggle={toggleTodo}
              onDelete={deleteTodo}
            />
          ))
        )}
      </div>
    </div>
  );
}

Congratulations!

You've just built a complete todo app using light-hooks! Here's what you accomplished:

  • Persistent Storage: Todos survive page refreshes with useLocalStorage
  • Smart Toggles: Show/hide completed items with useToggle
  • Statistics: View counting with useCounter
  • Optimized Search: Debounced filtering with useDebounce

Note:

Pro Tip: Your app automatically handles edge cases like empty states, persistence, and performance optimization - all thanks to light-hooks!

What's Next?

Now that you've mastered the basics, explore more hooks:

  • useInterval - for auto-refresh functionality
  • useTimeout - for delayed actions
  • usePrevious - to compare with previous values
  • useUpdateEffect - useEffect that skips first render

Ready to dive deeper? Check out our individual hook documentation for advanced patterns and real-world examples!

Key Takeaways

  1. Less Boilerplate: light-hooks eliminate common React patterns
  2. Built-in Persistence: useLocalStorage handles serialization automatically
  3. Performance Optimized: useDebounce prevents excessive function calls
  4. TypeScript Ready: Full type safety out of the box
  5. Tree Shakable: Import only what you need