React Coding Challenges & Solutions

201

Implement a simple Counter with + / − / Reset buttons using useState

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div style={{ textAlign: 'center', padding: '20px' }}>
      <h2>{count}</h2>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
      <button onClick={() => setCount(c => c - 1)} style={{ margin: '0 10px' }}>−1</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}
202

Controlled input that converts text to uppercase on every change

function UpperInput() {
  const [text, setText] = useState('');

  return (
    <input
      type="text"
      value={text}
      onChange={e => setText(e.target.value.toUpperCase())}
      placeholder="Type anything..."
      style={{ padding: '8px', width: '300px' }}
    />
  );
}
203

Fetch user from JSONPlaceholder on mount + loading/error states

function UserFetcher({ id = 1 }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
      .then(r => {
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        return r.json();
      })
      .then(setUser)
      .catch(err => setError(err.message))
      .finally(() => setLoading(false));
  }, [id]);

  if (loading) return <p>Loading user...</p>;
  if (error)   return <p style={{color:'red'}}>Error: {error}</p>;
  if (!user)   return null;

  return (
    <div>
      <h3>{user.name}</h3>
      <p>@{user.username} • {user.email}</p>
      <p>{user.company.name} — {user.company.catchPhrase}</p>
    </div>
  );
}
204

Simple toggle switch (checkbox styled as switch)

function Toggle() {
  const [on, setOn] = useState(false);

  return (
    <label style={{ display: 'inline-flex', alignItems: 'center', cursor: 'pointer' }}>
      <input
        type="checkbox"
        checked={on}
        onChange={() => setOn(v => !v)}
        style={{ display: 'none' }}
      />
      <span
        style={{
          width: '50px',
          height: '26px',
          background: on ? '#2196f3' : '#ccc',
          borderRadius: '13px',
          position: 'relative',
          transition: 'background 0.25s'
        }}
      >
        <span
          style={{
            position: 'absolute',
            width: '22px',
            height: '22px',
            background: 'white',
            borderRadius: '50%',
            top: '2px',
            left: on ? '24px' : '2px',
            transition: 'left 0.25s'
          }}
        />
      </span>
      <span style={{ marginLeft: '12px' }}>{on ? 'ON' : 'OFF'}</span>
    </label>
  );
}
205

Basic form with validation on submit (name, email, password)

function SignupForm() {
  const [values, setValues] = useState({ name: '', email: '', password: '' });
  const [errors, setErrors] = useState({});

  const handleChange = e => {
    setValues({ ...values, [e.target.name]: e.target.value });
  };

  const validate = () => {
    const errs = {};
    if (!values.name.trim()) errs.name = "Name is required";
    if (!/\S+@\S+\.\S+/.test(values.email)) errs.email = "Valid email required";
    if (values.password.length < 6) errs.password = "≥ 6 characters";
    return errs;
  };

  const handleSubmit = e => {
    e.preventDefault();
    const validation = validate();
    setErrors(validation);
    if (Object.keys(validation).length === 0) {
      alert("Form submitted:\n" + JSON.stringify(values, null, 2));
    }
  };

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: '400px' }}>
      <div>
        <input name="name" placeholder="Name" value={values.name} onChange={handleChange} />
        {errors.name && <span style={{color:'red', fontSize:'0.9em'}}>{errors.name}</span>}
      </div>
      <div>
        <input name="email" type="email" placeholder="Email" value={values.email} onChange={handleChange} />
        {errors.email && <span style={{color:'red', fontSize:'0.9em'}}>{errors.email}</span>}
      </div>
      <div>
        <input name="password" type="password" placeholder="Password" value={values.password} onChange={handleChange} />
        {errors.password && <span style={{color:'red', fontSize:'0.9em'}}>{errors.password}</span>}
      </div>
      <button type="submit">Sign Up</button>
    </form>
  );
}
207

Search/filter list using useMemo for performance

function FilterList({ items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig'] }) {
  const [query, setQuery] = useState('');

  const filtered = useMemo(() => {
    if (!query.trim()) return items;
    const q = query.toLowerCase();
    return items.filter(item => item.toLowerCase().includes(q));
  }, [items, query]);

  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Search fruits..."
        style={{ width: '100%', padding: '8px', marginBottom: '12px' }}
      />
      <ul>
        {filtered.map((item, i) => <li key={i}>{item}</li>)}
        {!filtered.length && query && <li style={{color:'gray'}}>No matches</li>}
      </ul>
    </div>
  );
}
208

Custom hook: useLocalStorage (sync state ↔ localStorage)

function useLocalStorage(key, defaultValue) {
  const [stored, setStored] = useState(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : defaultValue;
    } catch {
      return defaultValue;
    }
  });

  const setValue = value => {
    try {
      const toStore = value instanceof Function ? value(stored) : value;
      setStored(toStore);
      localStorage.setItem(key, JSON.stringify(toStore));
    } catch (err) {
      console.error("localStorage write failed:", err);
    }
  };

  return [stored, setValue];
}

// Usage:
// const [theme, setTheme] = useLocalStorage('theme', 'light');
209

Live digital clock using useEffect + setInterval

function LiveClock() {
  const [now, setNow] = useState(new Date());

  useEffect(() => {
    const id = setInterval(() => setNow(new Date()), 1000);
    return () => clearInterval(id);
  }, []);

  return (
    <h2 style={{ fontFamily: 'monospace', textAlign: 'center' }}>
      {now.toLocaleTimeString()}
    </h2>
  );
}
210

Custom hook usePrevious – track previous value of a variable

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  }, [value]);
  return ref.current;
}

// Example usage inside component:
function Example({ count }) {
  const prev = usePrevious(count);
  return (
    <p>
      Current: {count} — Previous: {prev ?? '—'}
    </p>
  );
}
211

Accordion – only one item open at a time

function Accordion({ items }) {
  const [openIndex, setOpenIndex] = useState(-1);

  return (
    <div>
      {items.map((item, i) => (
        <div key={i}>
          <button
            onClick={() => setOpenIndex(openIndex === i ? -1 : i)}
            style={{ width: '100%', padding: '12px', textAlign: 'left' }}
          >
            {item.title} {openIndex === i ? '▲' : '▼'}
          </button>
          {openIndex === i && (
            <div style={{ padding: '12px', background: '#f8f9fa' }}>
              {item.content}
            </div>
          )}
        </div>
      ))}
    </div>
  );
}

// Usage: <Accordion items={[{title:'Q1', content:'A1'}, ...]} />
212

Basic Tabs component with active tab state

function Tabs({ tabs }) {
  const [active, setActive] = useState(0);

  return (
    <div>
      <div style={{ display: 'flex', borderBottom: '1px solid #ccc' }}>
        {tabs.map((tab, i) => (
          <button
            key={i}
            onClick={() => setActive(i)}
            style={{
              padding: '12px 24px',
              border: 'none',
              background: active === i ? '#007bff' : 'transparent',
              color: active === i ? 'white' : '#333',
              cursor: 'pointer'
            }}
          >
            {tab.label}
          </button>
        ))}
      </div>
      <div style={{ padding: '20px' }}>
        {tabs[active].content}
      </div>
    </div>
  );
}
213

Implement basic infinite scroll using IntersectionObserver

function InfiniteScrollList() {
  const [items, setItems] = useState(Array.from({length: 20}, (_, i) => `Item ${i + 1}`));
  const [page, setPage] = useState(1);
  const loader = useRef(null);

  const loadMore = useCallback(() => {
    setTimeout(() => {
      const newItems = Array.from({length: 10}, (_, i) => `Item ${items.length + i + 1}`);
      setItems(prev => [...prev, ...newItems]);
      setPage(p => p + 1);
    }, 800);
  }, [items.length]);

  useEffect(() => {
    const observer = new IntersectionObserver(
      entries => { if (entries[0].isIntersecting) loadMore(); },
      { threshold: 1.0 }
    );
    if (loader.current) observer.observe(loader.current);
    return () => observer.disconnect();
  }, [loadMore]);

  return (
    <div>
      <ul style={{ listStyle: 'none', padding: 0 }}>
        {items.map((item, i) => (
          <li key={i} style={{ padding: '16px', borderBottom: '1px solid #eee' }}>{item}</li>
        ))}
      </ul>
      <div ref={loader} style={{ height: '40px', textAlign: 'center', padding: '20px' }}>
        Loading more...
      </div>
    </div>
  );
}
214

Create a useDebounce custom hook

function useDebounce(value, delay = 500) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(handler);
  }, [value, delay]);

  return debouncedValue;
}

// Usage example:
function Search() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 600);

  useEffect(() => {
    if (debouncedQuery) console.log('Search API call with:', debouncedQuery);
  }, [debouncedQuery]);

  return <input value={query} onChange={e => setQuery(e.target.value)} />;
}
215

Modal dialog using React Portal and focus trap (basic version)

import { createPortal } from 'react-dom';

function Modal({ isOpen, onClose, children }) {
  const modalRef = useRef(null);

  useEffect(() => {
    if (!isOpen) return;
    const handleEsc = e => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', handleEsc);
    modalRef.current?.focus();
    return () => document.removeEventListener('keydown', handleEsc);
  }, [isOpen, onClose]);

  if (!isOpen) return null;

  return createPortal(
    <div
      style={{
        position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)',
        display: 'flex', alignItems: 'center', justifyContent: 'center'
      }}
      onClick={onClose}
    >
      <div
        ref={modalRef}
        tabIndex={-1}
        onClick={e => e.stopPropagation()}
        style={{
          background: 'white', padding: '24px', borderRadius: '8px',
          maxWidth: '500px', width: '90%', outline: 'none'
        }}
      >
        {children}
        <button onClick={onClose} style={{ marginTop: '16px' }}>Close</button>
      </div>
    </div>,
    document.body
  );
}

// Usage: <Modal isOpen={show} onClose={() => setShow(false)}>Content</Modal>
216

Basic drag-and-drop list reordering (useState + HTML5 drag events)

function DraggableList() {
  const [items, setItems] = useState(['Task 1', 'Task 2', 'Task 3', 'Task 4']);

  const [dragIndex, setDragIndex] = useState(null);

  const handleDragStart = (e, index) => {
    setDragIndex(index);
    e.dataTransfer.effectAllowed = 'move';
  };

  const handleDragOver = e => {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
  };

  const handleDrop = (e, dropIndex) => {
    e.preventDefault();
    if (dragIndex === null || dragIndex === dropIndex) return;

    const newItems = [...items];
    const [draggedItem] = newItems.splice(dragIndex, 1);
    newItems.splice(dropIndex, 0, draggedItem);
    setItems(newItems);
    setDragIndex(null);
  };

  return (
    <ul style={{ listStyle: 'none', padding: 0 }}>
      {items.map((item, i) => (
        <li
          key={i}
          draggable
          onDragStart={e => handleDragStart(e, i)}
          onDragOver={handleDragOver}
          onDrop={e => handleDrop(e, i)}
          style={{
            padding: '12px',
            margin: '8px 0',
            background: dragIndex === i ? '#e0f7ff' : '#f8f9fa',
            border: '1px solid #ddd',
            borderRadius: '4px',
            cursor: 'grab'
          }}
        >
          {item}
        </li>
      ))}
    </ul>
  );
}
217

Star rating component (click to set rating)

function StarRating({ max = 5, onRate }) {
  const [rating, setRating] = useState(0);
  const [hover, setHover] = useState(0);

  return (
    <div style={{ display: 'flex', gap: '4px' }}>
      {Array.from({ length: max }, (_, i) => {
        const starValue = i + 1;
        return (
          <span
            key={i}
            onClick={() => {
              setRating(starValue);
              onRate?.(starValue);
            }}
            onMouseEnter={() => setHover(starValue)}
            onMouseLeave={() => setHover(0)}
            style={{
              fontSize: '28px',
              color: starValue <= (hover || rating) ? '#ffc107' : '#e4e5e9',
              cursor: 'pointer'
            }}
          >
            ★
          </span>
        );
      })}
    </div>
  );
}

// Usage: <StarRating onRate={rating => console.log(rating)} />
219

Image preview before upload (file input + URL.createObjectURL)

function ImagePreview() {
  const [preview, setPreview] = useState(null);

  const handleFile = e => {
    const file = e.target.files[0];
    if (!file) return;
    const url = URL.createObjectURL(file);
    setPreview(url);

    return () => URL.revokeObjectURL(url); // cleanup
  };

  return (
    <div>
      <input type="file" accept="image/*" onChange={handleFile} />
      {preview && (
        <img
          src={preview}
          alt="Preview"
          style={{ maxWidth: '300px', marginTop: '16px', borderRadius: '8px' }}
        />
      )}
    </div>
  );
}
221

Debounced search input that triggers API call only after pause

function useDebounce(value, delay = 500) {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debounced;
}

function SearchWithDebounce() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 600);

  useEffect(() => {
    if (!debouncedQuery) return;
    console.log('API call → search for:', debouncedQuery);
    // fetch(`/api/search?q=${debouncedQuery}`).then(...)
  }, [debouncedQuery]);

  return (
    <input
      value={query}
      onChange={e => setQuery(e.target.value)}
      placeholder="Search products..."
      style={{ width: '100%', padding: '12px' }}
    />
  );
}
222

Custom useFetch hook with loading/error/data + abort

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    setLoading(true);
    fetch(url, { signal: controller.signal })
      .then(r => {
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        return r.json();
      })
      .then(setData)
      .catch(err => {
        if (err.name !== 'AbortError') setError(err.message);
      })
      .finally(() => setLoading(false));

    return () => controller.abort();
  }, [url]);

  return { data, loading, error };
}

// Usage:
// const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/posts/1');
223

useClickOutside custom hook (close modal/dropdown on outside click)

function useClickOutside(ref, handler) {
  useEffect(() => {
    const listener = event => {
      if (!ref.current || ref.current.contains(event.target)) return;
      handler(event);
    };

    document.addEventListener('mousedown', listener);
    document.addEventListener('touchstart', listener);

    return () => {
      document.removeEventListener('mousedown', listener);
      document.removeEventListener('touchstart', listener);
    };
  }, [ref, handler]);
}

// Usage example:
function Dropdown() {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);

  useClickOutside(ref, () => setOpen(false));

  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button onClick={() => setOpen(!open)}>Menu</button>
      {open && (
        <div style={{ position: 'absolute', background: 'white', border: '1px solid #ccc', padding: '12px' }}>
          <p>Option 1</p>
          <p>Option 2</p>
        </div>
      )}
    </div>
  );
}
224

useWindowSize hook – track window dimensions

function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setSize({ width: window.innerWidth, height: window.innerHeight });
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return size;
}

// Usage:
// const { width, height } = useWindowSize();
225

Copy to clipboard button with fallback

function CopyButton({ text }) {
  const [copied, setCopied] = useState(false);

  const copy = async () => {
    try {
      await navigator.clipboard.writeText(text);
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch (err) {
      console.error('Clipboard failed', err);
      // fallback
      const textarea = document.createElement('textarea');
      textarea.value = text;
      document.body.appendChild(textarea);
      textarea.select();
      document.execCommand('copy');
      document.body.removeChild(textarea);
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    }
  };

  return (
    <button onClick={copy} style={{ padding: '8px 16px' }}>
      {copied ? 'Copied!' : 'Copy'}
    </button>
  );
}
226

Dark/light mode toggle with localStorage persistence

function ThemeToggle() {
  const [theme, setTheme] = useState(() => {
    return localStorage.getItem('theme') || 'light';
  });

  useEffect(() => {
    document.documentElement.className = theme;
    localStorage.setItem('theme', theme);
  }, [theme]);

  const toggle = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return (
    <button onClick={toggle} style={{ padding: '10px 20px' }}>
      Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode
    </button>
  );
}

// Add to index.html or root layout:
// <html class="light"> (initial)
227

Dynamic form – add/remove input fields (array state)

function DynamicForm() {
  const [fields, setFields] = useState([{ id: 1, value: '' }]);

  const addField = () => {
    setFields([...fields, { id: Date.now(), value: '' }]);
  };

  const removeField = id => {
    if (fields.length === 1) return;
    setFields(fields.filter(f => f.id !== id));
  };

  const updateField = (id, value) => {
    setFields(fields.map(f => f.id === id ? { ...f, value } : f));
  };

  return (
    <div>
      {fields.map(field => (
        <div key={field.id} style={{ display: 'flex', marginBottom: '12px' }}>
          <input
            value={field.value}
            onChange={e => updateField(field.id, e.target.value)}
            placeholder="Enter value"
            style={{ flex: 1, marginRight: '8px' }}
          />
          <button onClick={() => removeField(field.id)} style={{ color: 'red' }}>Remove</button>
        </div>
      ))}
      <button onClick={addField}>Add Field</button>
    </div>
  );
}
228

Simple image carousel with next/prev buttons

function Carousel({ images }) {
  const [index, setIndex] = useState(0);

  const next = () => setIndex(i => (i + 1) % images.length);
  const prev = () => setIndex(i => (i - 1 + images.length) % images.length);

  return (
    <div style={{ position: 'relative', maxWidth: '600px', margin: 'auto' }}>
      <img
        src={images[index]}
        alt={`Slide ${index + 1}`}
        style={{ width: '100%', height: '400px', objectFit: 'cover', borderRadius: '8px' }}
      />
      <button
        onClick={prev}
        style={{ position: 'absolute', left: '10px', top: '50%', transform: 'translateY(-50%)' }}
      >←</button>
      <button
        onClick={next}
        style={{ position: 'absolute', right: '10px', top: '50%', transform: 'translateY(-50%)' }}
      >→</button>
      <div style={{ textAlign: 'center', marginTop: '12px' }}>
        {index + 1} / {images.length}
      </div>
    </div>
  );
}

// Usage: <Carousel images={['img1.jpg', 'img2.jpg', ...]} />
229

Countdown timer (e.g. 60 seconds)

function Countdown({ initial = 60 }) {
  const [seconds, setSeconds] = useState(initial);

  useEffect(() => {
    if (seconds <= 0) return;

    const id = setInterval(() => {
      setSeconds(s => s - 1);
    }, 1000);

    return () => clearInterval(id);
  }, [seconds]);

  return (
    <div style={{ fontSize: '3rem', textAlign: 'center' }}>
      {seconds === 0 ? 'Time’s up!' : seconds}
      <button onClick={() => setSeconds(initial)} style={{ marginLeft: '20px', fontSize: '1rem' }}>
        Restart
      </button>
    </div>
  );
}
230

Basic typeahead / autocomplete input

function Typeahead({ suggestions = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'] }) {
  const [input, setInput] = useState('');
  const [show, setShow] = useState(false);

  const filtered = suggestions.filter(s =>
    s.toLowerCase().startsWith(input.toLowerCase())
  );

  return (
    <div style={{ position: 'relative', width: '300px' }}>
      <input
        value={input}
        onChange={e => { setInput(e.target.value); setShow(true); }}
        onFocus={() => setShow(true)}
        onBlur={() => setTimeout(() => setShow(false), 100)}
        placeholder="Search fruit..."
      />
      {show && filtered.length > 0 && (
        <ul style={{
          position: 'absolute',
          width: '100%',
          maxHeight: '200px',
          overflowY: 'auto',
          background: 'white',
          border: '1px solid #ccc',
          margin: 0,
          padding: 0,
          listStyle: 'none',
          zIndex: 10
        }}>
          {filtered.map((item, i) => (
            <li
              key={i}
              onClick={() => { setInput(item); setShow(false); }}
              style={{ padding: '8px', cursor: 'pointer' }}
              onMouseDown={e => e.preventDefault()}
            >
              {item}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}
231

Simplified virtualized list (only render visible items)

function VirtualList({ items, itemHeight = 50, height = 400 }) {
  const [scrollTop, setScrollTop] = useState(0);
  const containerRef = useRef(null);

  const visibleCount = Math.ceil(height / itemHeight) + 2; // overscan
  const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - 1);
  const visibleItems = items.slice(startIndex, startIndex + visibleCount);

  return (
    <div
      ref={containerRef}
      onScroll={e => setScrollTop(e.target.scrollTop)}
      style={{ height: `${height}px`, overflowY: 'auto', position: 'relative' }}
    >
      <div style={{ height: `${items.length * itemHeight}px`, position: 'relative' }}>
        {visibleItems.map((item, i) => (
          <div
            key={startIndex + i}
            style={{
              position: 'absolute',
              top: `${(startIndex + i) * itemHeight}px`,
              height: `${itemHeight}px`,
              width: '100%',
              padding: '12px',
              boxSizing: 'border-box',
              borderBottom: '1px solid #eee'
            }}
          >
            {item}
          </div>
        ))}
      </div>
    </div>
  );
}

// Usage: <VirtualList items={Array(1000).fill().map((_,i)=>`Row ${i+1}`)} />
232

Complex form state management with useReducer

const initialForm = { name: '', age: '', email: '', agree: false };

function formReducer(state, action) {
  switch (action.type) {
    case 'update':
      return { ...state, [action.field]: action.value };
    case 'reset':
      return initialForm;
    default:
      return state;
  }
}

function ComplexForm() {
  const [form, dispatch] = useReducer(formReducer, initialForm);

  const isValid = form.name && form.age > 0 && form.email.includes('@') && form.agree;

  return (
    <form style={{ maxWidth: '400px' }}>
      <input
        placeholder="Name"
        value={form.name}
        onChange={e => dispatch({ type: 'update', field: 'name', value: e.target.value })}
      />
      <input
        type="number"
        placeholder="Age"
        value={form.age}
        onChange={e => dispatch({ type: 'update', field: 'age', value: Number(e.target.value) })}
      />
      <input
        placeholder="Email"
        value={form.email}
        onChange={e => dispatch({ type: 'update', field: 'email', value: e.target.value })}
      />
      <label>
        <input
          type="checkbox"
          checked={form.agree}
          onChange={e => dispatch({ type: 'update', field: 'agree', value: e.target.checked })}
        />
        I agree to terms
      </label>
      <button disabled={!isValid} onClick={() => alert('Submitted!')}>
        Submit
      </button>
      <button type="button" onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </form>
  );
}
233

Optimistic like button (increase count immediately, rollback on error)

function LikeButton({ initialLikes = 42 }) {
  const [likes, setLikes] = useState(initialLikes);
  const [optimistic, setOptimistic] = useState(false);
  const [error, setError] = useState(null);

  const handleLike = async () => {
    const previous = likes;
    setOptimistic(true);
    setLikes(likes + 1);

    try {
      // simulate API call
      await new Promise((res, rej) => setTimeout(() => rej(new Error('Failed')), 1200));
      // await api.likePost();
    } catch (err) {
      setError('Failed to like');
      setLikes(previous);
    } finally {
      setOptimistic(false);
    }
  };

  return (
    <button
      onClick={handleLike}
      disabled={optimistic}
      style={{ padding: '8px 16px', background: optimistic ? '#ccc' : '#ff4081' }}
    >
      ❤️ {likes}
      {optimistic && ' (liking...)'}
      {error && <span style={{ color: 'red', marginLeft: '8px' }}>{error}</span>}
    </button>
  );
}
234

Hover card that follows mouse position

function HoverCard() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [visible, setVisible] = useState(false);

  const handleMouseMove = e => {
    setPosition({ x: e.clientX + 16, y: e.clientY + 16 });
  };

  return (
    <div
      onMouseEnter={() => setVisible(true)}
      onMouseLeave={() => setVisible(false)}
      onMouseMove={handleMouseMove}
      style={{ position: 'relative', display: 'inline-block' }}
    >
      <span style={{ cursor: 'pointer', padding: '8px 16px', background: '#f0f0f0' }}>
        Hover me
      </span>

      {visible && (
        <div
          style={{
            position: 'fixed',
            left: position.x,
            top: position.y,
            background: 'white',
            border: '1px solid #ccc',
            padding: '12px',
            borderRadius: '6px',
            boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
            pointerEvents: 'none',
            zIndex: 1000
          }}
        >
          <h4>Tooltip</h4>
          <p>This follows your mouse!</p>
        </div>
      )}
    </div>
  );
}
235

Password strength indicator (weak / medium / strong)

function PasswordStrength() {
  const [password, setPassword] = useState('');

  const getStrength = () => {
    if (password.length < 6) return { text: 'Weak', color: 'red' };
    if (password.length < 10) return { text: 'Medium', color: 'orange' };
    if (/[A-Z]/.test(password) && /[0-9]/.test(password) && /[^A-Za-z0-9]/.test(password)) {
      return { text: 'Strong', color: 'green' };
    }
    return { text: 'Medium', color: 'orange' };
  };

  const strength = getStrength();

  return (
    <div>
      <input
        type="password"
        value={password}
        onChange={e => setPassword(e.target.value)}
        placeholder="Enter password"
        style={{ width: '300px', padding: '10px' }}
      />
      <div style={{ marginTop: '8px', color: strength.color, fontWeight: 'bold' }}>
        Strength: {strength.text}
      </div>
      <div style={{ marginTop: '8px', color: '#666', fontSize: '0.9em' }}>
        {password.length} characters
      </div>
    </div>
  );
}
236

Manual infinite scroll with page number & loader

function ManualInfinite() {
  const [posts, setPosts] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);

  const fetchPosts = useCallback(async () => {
    if (!hasMore || loading) return;
    setLoading(true);

    try {
      const res = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=10`);
      const data = await res.json();
      setPosts(prev => [...prev, ...data]);
      setHasMore(data.length === 10);
    } catch (err) {
      console.error(err);
    } finally {
      setLoading(false);
    }
  }, [page, hasMore, loading]);

  useEffect(() => {
    fetchPosts();
  }, [page, fetchPosts]);

  const handleScroll = () => {
    if (window.innerHeight + document.documentElement.scrollTop >= document.documentElement.offsetHeight - 100) {
      if (!loading && hasMore) setPage(p => p + 1);
    }
  };

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [loading, hasMore]);

  return (
    <div>
      {posts.map(post => (
        <div key={post.id} style={{ padding: '16px', borderBottom: '1px solid #eee' }}>
          <h4>{post.title}</h4>
          <p>{post.body.substring(0, 100)}...</p>
        </div>
      ))}
      {loading && <p style={{ textAlign: 'center', padding: '20px' }}>Loading...</p>}
      {!hasMore && <p style={{ textAlign: 'center', color: '#888' }}>No more posts</p>}
    </div>
  );
}
237

Responsive masonry / Pinterest-style grid (CSS columns)

function MasonryGrid({ items }) {
  return (
    <div style={{
      columnCount: 4,
      columnGap: '16px',
      '@media (max-width: 1200px)': { columnCount: 3 },
      '@media (max-width: 900px)': { columnCount: 2 },
      '@media (max-width: 600px)': { columnCount: 1 }
    }}>
      {items.map((item, i) => (
        <div key={i} style={{
          breakInside: 'avoid',
          marginBottom: '16px',
          background: '#f8f9fa',
          borderRadius: '8px',
          overflow: 'hidden'
        }}>
          <img src={item.image} alt="" style={{ width: '100%', display: 'block' }} />
          <p style={{ padding: '12px' }}>{item.title}</p>
        </div>
      ))}
    </div>
  );
}

// Note: For production, consider react-masonry-css or masonry layout library
238

Simple toast notification system

function ToastContainer() {
  const [toasts, setToasts] = useState([]);

  const addToast = (message, type = 'info') => {
    const id = Date.now();
    setToasts(prev => [...prev, { id, message, type }]);
    setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 4000);
  };

  return (
    <>
      <div style={{ position: 'fixed', top: '16px', right: '16px', zIndex: 1000 }}>
        {toasts.map(t => (
          <div
            key={t.id}
            style={{
              background: t.type === 'error' ? '#f44336' : t.type === 'success' ? '#4caf50' : '#2196f3',
              color: 'white',
              padding: '12px 20px',
              marginBottom: '8px',
              borderRadius: '6px',
              boxShadow: '0 4px 12px rgba(0,0,0,0.2)'
            }}
          >
            {t.message}
          </div>
        ))}
      </div>
      <button onClick={() => addToast('Success! Item added.', 'success')}>Show Success</button>
      <button onClick={() => addToast('Error occurred!', 'error')}>Show Error</button>
    </>
  );
}
239

useKeyPress hook – detect specific key globally

function useKeyPress(targetKey) {
  const [keyPressed, setKeyPressed] = useState(false);

  useEffect(() => {
    const down = e => {
      if (e.key === targetKey) setKeyPressed(true);
    };
    const up = e => {
      if (e.key === targetKey) setKeyPressed(false);
    };

    window.addEventListener('keydown', down);
    window.addEventListener('keyup', up);
    return () => {
      window.removeEventListener('keydown', down);
      window.removeEventListener('keyup', up);
    };
  }, [targetKey]);

  return keyPressed;
}

// Usage:
// const escPressed = useKeyPress('Escape');
// useEffect(() => { if (escPressed) onClose(); }, [escPressed]);
241

Nested comments / threaded reply component

function Comment({ comment, depth = 0 }) {
  const [showReplies, setShowReplies] = useState(true);

  return (
    <div style={{ marginLeft: `${depth * 24}px`, marginBottom: '16px', borderLeft: depth ? '2px solid #ddd' : 'none', paddingLeft: depth ? '16px' : '0' }}>
      <p><strong>{comment.author}</strong>: {comment.text}</p>
      {comment.replies?.length > 0 && (
        <>
          <button onClick={() => setShowReplies(!showReplies)} style={{ fontSize: '0.9em' }}>
            {showReplies ? 'Hide' : 'Show'} {comment.replies.length} replies
          </button>
          {showReplies && (
            <div style={{ marginTop: '8px' }}>
              {comment.replies.map((reply, i) => (
                <Comment key={i} comment={reply} depth={depth + 1} />
              ))}
            </div>
          )}
        </>
      )}
    </div>
  );
}

// Usage: <Comment comment={sampleThread} />
242

Basic color picker with hex input

function ColorPicker() {
  const [color, setColor] = useState('#ff4081');

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
      <div style={{
        width: '120px',
        height: '120px',
        backgroundColor: color,
        borderRadius: '12px',
        boxShadow: '0 4px 12px rgba(0,0,0,0.15)'
      }} />
      <input
        type="color"
        value={color}
        onChange={e => setColor(e.target.value)}
      />
      <input
        type="text"
        value={color}
        onChange={e => {
          const val = e.target.value;
          if (/^#[0-9A-Fa-f]{6}$/.test(val)) setColor(val);
        }}
        placeholder="#ff4081"
        maxLength={7}
      />
      <p>Selected: {color}</p>
    </div>
  );
}
243

Dual-thumb range slider (min-max selection)

function DualRangeSlider({ min = 0, max = 100, step = 1 }) {
  const [low, setLow] = useState(min);
  const [high, setHigh] = useState(max);

  const handleLow = e => {
    const val = Math.min(Number(e.target.value), high - step);
    setLow(val);
  };

  const handleHigh = e => {
    const val = Math.max(Number(e.target.value), low + step);
    setHigh(val);
  };

  return (
    <div style={{ padding: '20px', maxWidth: '400px' }}>
      <div style={{ position: 'relative', height: '40px' }}>
        <input
          type="range"
          min={min}
          max={max}
          step={step}
          value={low}
          onChange={handleLow}
          style={{ position: 'absolute', width: '100%', zIndex: 1 }}
        />
        <input
          type="range"
          min={min}
          max={max}
          step={step}
          value={high}
          onChange={handleHigh}
          style={{ position: 'absolute', width: '100%', zIndex: 2, opacity: 0.9 }}
        />
      </div>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '8px' }}>
        <span>{low}</span>
        <span>{high}</span>
      </div>
    </div>
  );
}
244

Sortable table with column headers

function SortableTable({ data }) {
  const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' });

  const sortedData = React.useMemo(() => {
    if (!sortConfig.key) return data;

    return [...data].sort((a, b) => {
      if (a[sortConfig.key] < b[sortConfig.key]) return sortConfig.direction === 'asc' ? -1 : 1;
      if (a[sortConfig.key] > b[sortConfig.key]) return sortConfig.direction === 'asc' ? 1 : -1;
      return 0;
    });
  }, [data, sortConfig]);

  const requestSort = key => {
    let direction = 'asc';
    if (sortConfig.key === key && sortConfig.direction === 'asc') {
      direction = 'desc';
    }
    setSortConfig({ key, direction });
  };

  return (
    <table style={{ width: '100%', borderCollapse: 'collapse' }}>
      <thead>
        <tr>
          {Object.keys(data[0] || {}).map(key => (
            <th
              key={key}
              onClick={() => requestSort(key)}
              style={{ padding: '12px', cursor: 'pointer', background: '#f0f0f0' }}
            >
              {key} {sortConfig.key === key ? (sortConfig.direction === 'asc' ? '↑' : '↓') : ''}
            </th>
          ))}
        </tr>
      </thead>
      <tbody>
        {sortedData.map((row, i) => (
          <tr key={i}>
            {Object.values(row).map((val, j) => (
              <td key={j} style={{ padding: '12px', borderBottom: '1px solid #eee' }}>{val}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}
245

Resizable split pane (drag divider)

function SplitPane({ left, right }) {
  const [width, setWidth] = useState(50); // percentage
  const dividerRef = useRef(null);

  useEffect(() => {
    const handleMove = e => {
      if (!dividerRef.current) return;
      const container = dividerRef.current.parentElement;
      const rect = container.getBoundingClientRect();
      const newWidth = ((e.clientX - rect.left) / rect.width) * 100;
      setWidth(Math.max(10, Math.min(90, newWidth)));
    };

    const handleUp = () => {
      document.removeEventListener('mousemove', handleMove);
      document.removeEventListener('mouseup', handleUp);
    };

    const handleDown = () => {
      document.addEventListener('mousemove', handleMove);
      document.addEventListener('mouseup', handleUp);
    };

    const divider = dividerRef.current;
    divider?.addEventListener('mousedown', handleDown);
    return () => divider?.removeEventListener('mousedown', handleDown);
  }, []);

  return (
    <div style={{ display: 'flex', height: '400px', width: '100%' }}>
      <div style={{ width: `${width}%`, background: '#f0f8ff', padding: '16px', overflow: 'auto' }}>
        {left}
      </div>
      <div
        ref={dividerRef}
        style={{
          width: '8px',
          background: '#ccc',
          cursor: 'col-resize',
          userSelect: 'none'
        }}
      />
      <div style={{ width: `${100 - width}%`, background: '#fff0f0', padding: '16px', overflow: 'auto' }}>
        {right}
      </div>
    </div>
  );
}

// Usage: <SplitPane left={} right={} />
246

useOnline hook – detect internet connection status

function useOnline() {
  const [online, setOnline] = useState(navigator.onLine);

  useEffect(() => {
    const handleOnline = () => setOnline(true);
    const handleOffline = () => setOnline(false);

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  return online;
}

// Usage:
// const isOnline = useOnline();
// <p>You are currently {isOnline ? 'online' : 'offline'}</p>
247

Lazy image loading with placeholder & IntersectionObserver

function LazyImage({ src, alt, placeholder = 'https://via.placeholder.com/300x200' }) {
  const [loaded, setLoaded] = useState(false);
  const imgRef = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setLoaded(true);
          observer.disconnect();
        }
      },
      { rootMargin: '100px' }
    );

    if (imgRef.current) observer.observe(imgRef.current);
    return () => observer.disconnect();
  }, []);

  return (
    <img
      ref={imgRef}
      src={loaded ? src : placeholder}
      alt={alt}
      style={{ width: '100%', height: 'auto', transition: 'opacity 0.3s' }}
      onLoad={() => setLoaded(true)}
    />
  );
}

// Usage: <LazyImage src="real-image.jpg" alt="description" />
248

File upload with progress bar & multiple files

function FileUploader() {
  const [files, setFiles] = useState([]);
  const [progress, setProgress] = useState({});

  const handleFiles = e => {
    const selected = Array.from(e.target.files);
    setFiles(prev => [...prev, ...selected]);
  };

  const uploadFile = async file => {
    const id = file.name + Date.now();
    setProgress(prev => ({ ...prev, [id]: 0 }));

    // simulate upload
    return new Promise(resolve => {
      let pct = 0;
      const interval = setInterval(() => {
        pct += 10;
        setProgress(prev => ({ ...prev, [id]: pct }));
        if (pct >= 100) {
          clearInterval(interval);
          resolve();
        }
      }, 300);
    });
  };

  const startUpload = () => {
    files.forEach(file => uploadFile(file));
  };

  return (
    <div>
      <input type="file" multiple onChange={handleFiles} />
      <ul>
        {files.map((file, i) => (
          <li key={i}>
            {file.name} – {progress[file.name + i] || 0}%
          </li>
        ))}
      </ul>
      <button onClick={startUpload} disabled={!files.length}>Upload All</button>
    </div>
  );
}
249

Chat input with "user is typing..." simulation (real-time feel)

function ChatInput({ onSend }) {
  const [message, setMessage] = useState('');
  const [isTyping, setIsTyping] = useState(false);
  const timeoutRef = useRef(null);

  const handleChange = (e) => {
    const text = e.target.value;
    setMessage(text);

    // Show "typing..." immediately
    setIsTyping(true);

    // Clear previous timeout
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }

    // Hide "typing..." after 2 seconds of inactivity
    timeoutRef.current = setTimeout(() => {
      setIsTyping(false);
    }, 2000);
  };

  const handleSend = () => {
    if (!message.trim()) return;
    
    onSend(message.trim());
    setMessage('');
    setIsTyping(false);
    
    // Clear any pending timeout
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }
  };

  const handleKeyDown = (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      handleSend();
    }
  };

  return (
    <div style={{ position: 'relative', padding: '12px', background: '#f8f9fa', borderTop: '1px solid #ddd' }}>
      <textarea
        value={message}
        onChange={handleChange}
        onKeyDown={handleKeyDown}
        placeholder="Type a message..."
        rows={1}
        style={{
          width: '100%',
          minHeight: '44px',
          padding: '12px',
          borderRadius: '20px',
          border: '1px solid #ccc',
          resize: 'none',
          fontSize: '16px'
        }}
      />
      
      {isTyping && message.trim() && (
        <small 
          style={{
            position: 'absolute',
            bottom: '4px',
            left: '24px',
            color: '#666',
            fontSize: '0.85em',
            pointerEvents: 'none'
          }}
        >
          Typing...
        </small>
      )}

      <button
        onClick={handleSend}
        disabled={!message.trim()}
        style={{
          position: 'absolute',
          right: '20px',
          bottom: '20px',
          padding: '8px 16px',
          background: message.trim() ? '#0084ff' : '#ccc',
          color: 'white',
          border: 'none',
          borderRadius: '20px',
          cursor: message.trim() ? 'pointer' : 'not-allowed'
        }}
      >
        Send
      </button>
    </div>
  );
}

// Example parent usage:
// function Chat() {
//   const [messages, setMessages] = useState([]);
//   const handleSend = (text) => setMessages(prev => [...prev, { text, from: 'me' }]);
//   return (
//     <>
//       <div style={{ height: '400px', overflowY: 'auto' }}>{messages.map((m,i) => <p key={i}>{m.text}</p>)}</div>
//       <ChatInput onSend={handleSend} />
//     </>
//   );
// }
250

Custom useThrottle hook – limit function calls in time window

function useThrottle(callback, delay = 300) {
  const lastCallTime = useRef(0);
  const timeoutId = useRef(null);

  const throttledFunction = useCallback((...args) => {
    const now = Date.now();

    // If enough time has passed since last execution → run immediately
    if (now - lastCallTime.current >= delay) {
      callback(...args);
      lastCallTime.current = now;

      // Clear any pending timeout
      if (timeoutId.current) {
        clearTimeout(timeoutId.current);
        timeoutId.current = null;
      }
    } 
    // Otherwise schedule it for the end of the current delay window
    else if (!timeoutId.current) {
      timeoutId.current = setTimeout(() => {
        callback(...args);
        lastCallTime.current = Date.now();
        timeoutId.current = null;
      }, delay - (now - lastCallTime.current));
    }
  }, [callback, delay]);

  // Cleanup timeout on unmount
  useEffect(() => {
    return () => {
      if (timeoutId.current) {
        clearTimeout(timeoutId.current);
      }
    };
  }, []);

  return throttledFunction;
}

// ──────────────────────────────────────────────
//          Example usage: throttle scroll handler
// ──────────────────────────────────────────────

function ThrottledScrollDemo() {
  const handleScroll = useThrottle(() => {
    console.log('Scrolled! Current scrollY:', window.scrollY);
  }, 400);

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [handleScroll]);

  return (
    <div style={{ height: '2000px', background: 'linear-gradient(to bottom, #f0f8ff, #e6f2ff)' }}>
      <h2 style={{ position: 'fixed', top: '20px', left: '20px', background: 'white', padding: '12px 20px', borderRadius: '8px', boxShadow: '0 2px 10px rgba(0,0,0,0.1)' }}>
        Scroll me ↓ (console logs throttled to ~every 400ms)
      </h2>
    </div>
  );
}