iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

React Exercises

Three short React drills - custom hook, controlled form, and a tiny state machine.

Three short challenges

EXAMPLE
// 1. Custom hook - usePrevious
import { useEffect, useRef } from 'react';

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

function Counter() {
  const [n, setN] = useState(0);
  const prev = usePrevious(n);
  return <button onClick={() => setN(n + 1)}>n={n}, prev={prev ?? 'none'}</button>;
}


// 2. Controlled form with validation - no library
import { useState } from 'react';

type Errors = Partial<Record<'email' | 'password', string>>;

function Login({ onSubmit }: { onSubmit: (v: { email: string; password: string }) => Promise<void> }) {
  const [v, setV] = useState({ email: '', password: '' });
  const [errors, setErrors] = useState<Errors>({});
  const [busy, setBusy] = useState(false);

  const validate = (): Errors => {
    const e: Errors = {};
    if (!/^[^@]+@[^@]+\.[^@]+$/.test(v.email)) e.email = 'Enter a valid email';
    if (v.password.length < 8) e.password = 'At least 8 characters';
    return e;
  };

  const submit = async (ev: React.FormEvent) => {
    ev.preventDefault();
    const e = validate();
    setErrors(e);
    if (Object.keys(e).length) return;
    setBusy(true);
    try { await onSubmit(v); } finally { setBusy(false); }
  };

  return (
    <form onSubmit={submit} className='space-y-2'>
      <label>
        Email
        <input value={v.email} onChange={(e) => setV({ ...v, email: e.target.value })} />
        {errors.email && <span className='text-red-500'>{errors.email}</span>}
      </label>
      <label>
        Password
        <input type='password' value={v.password} onChange={(e) => setV({ ...v, password: e.target.value })} />
        {errors.password && <span className='text-red-500'>{errors.password}</span>}
      </label>
      <button disabled={busy}>Sign in</button>
    </form>
  );
}


// 3. Tiny state machine with useReducer
type State = { status: 'idle' | 'loading' | 'success' | 'error'; data?: any; error?: string };
type Action =
  | { type: 'fetch' }
  | { type: 'success'; data: any }
  | { type: 'error'; error: string };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'fetch':   return { status: 'loading' };
    case 'success': return { status: 'success', data: action.data };
    case 'error':   return { status: 'error', error: action.error };
  }
}

function User({ id }: { id: string }) {
  const [state, dispatch] = useReducer(reducer, { status: 'idle' });
  useEffect(() => {
    dispatch({ type: 'fetch' });
    fetch(\`/api/users/${id}\`)
      .then((r) => r.ok ? r.json() : Promise.reject(new Error(\`${r.status}\`)))
      .then((data) => dispatch({ type: 'success', data }))
      .catch((e) => dispatch({ type: 'error', error: String(e) }));
  }, [id]);
  if (state.status === 'loading') return <p>loading</p>;
  if (state.status === 'error')   return <p>error: {state.error}</p>;
  if (state.status === 'success') return <p>{state.data.name}</p>;
  return null;
}

Why it matters

Custom hooks for reuse, controlled forms for clarity, state machines for explicit transitions. These three patterns let you scale beyond a couple of useState calls without reaching for a library.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Fill in the blank.
function Greet(____) { return <h1>Hi, {props.name}</h1>; }
Try it Yourself »

Discussion

Loading…