Understanding React 19 New Hooks
Manoj Swami

Manoj Swami @manojspace

About: Senior Software Engineer

Location:
Gurgaon, India
Joined:
Jul 6, 2021

Understanding React 19 New Hooks

Publish Date: Jan 7
4 0

React 19 introduces several powerful new hooks that revolutionize how we handle forms and manage optimistic updates in our applications. In this blog, we'll explore useFormStatus, useActionState, and useOptimistic - three hooks that make our React applications more responsive and user-friendly.

useFormStatus: Enhanced Form Handling

The useFormStatus hook provides real-time information about form submissions, making it easier to create responsive and accessible forms. Let's explore how this hook improves upon React 18's form handling capabilities.

Example 1: Basic Form Loading State

function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <button disabled={pending}>
      {pending ? 'Submitting...' : 'Submit'}
    </button>
  );
}

function SignupForm() {
  return (
    <form action={async (formData) => {
      await submitSignupData(formData);
    }}>
      <input name="email" type="email" />
      <input name="password" type="password" />
      <SubmitButton />
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

In React 18, you'd need to manually manage loading states using useState. The new useFormStatus hook automatically handles this, reducing boilerplate code.

Example 2: Multiple Form States

function FormStatus() {
  const { pending, data, method } = useFormStatus();

  return (
    <div role="status">
      {pending && <span>Submitting via {method}...</span>}
      {!pending && data && <span>Last submission: {new Date().toLocaleString()}</span>}
    </div>
  );
}

function ContactForm() {
  return (
    <form action={async (formData) => {
      await submitContactForm(formData);
    }}>
      <textarea name="message" />
      <FormStatus />
      <SubmitButton />
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Example 3: Form Validation Status

function ValidationStatus() {
  const { pending, validationErrors } = useFormStatus();

  return (
    <div role="alert">
      {validationErrors?.map((error, index) => (
        <p key={index} className="error">{error}</p>
      ))}
    </div>
  );
}

function RegistrationForm() {
  return (
    <form action={async (formData) => {
      const errors = validateRegistration(formData);
      if (errors.length) throw errors;
      await register(formData);
    }}>
      <input name="username" />
      <input name="email" type="email" />
      <ValidationStatus />
      <SubmitButton />
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Example 4: Multi-Step Form Progress

function FormProgress() {
  const { pending, step, totalSteps } = useFormStatus();

  return (
    <div className="progress-bar">
      <div 
        className="progress" 
        style={{width: `${(step / totalSteps) * 100}%`}}
      />
      <span>Step {step} of {totalSteps}</span>
    </div>
  );
}

function WizardForm() {
  return (
    <form action={async (formData) => {
      await submitWizardData(formData, { steps: 3 });
    }}>
      {/* Form steps */}
      <FormProgress />
      <SubmitButton />
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Example 5: File Upload Progress

function UploadProgress() {
  const { pending, progress } = useFormStatus();

  return (
    <div>
      {pending && progress && (
        <div className="upload-progress">
          <div 
            className="progress-bar" 
            style={{width: `${progress}%`}}
          />
          <span>{progress}% uploaded</span>
        </div>
      )}
    </div>
  );
}

function FileUploadForm() {
  return (
    <form action={async (formData) => {
      await uploadFile(formData);
    }}>
      <input type="file" name="document" />
      <UploadProgress />
      <SubmitButton />
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

useActionState: Managing Action Results

The useActionState hook provides a way to track the state of form actions and server mutations, making it easier to handle success and error states.

Example 1: Basic Action State

function SubmissionStatus() {
  const state = useActionState();

  return (
    <div>
      {state.status === 'success' && <p>Submission successful!</p>}
      {state.status === 'error' && <p>Error: {state.error.message}</p>}
    </div>
  );
}

function CommentForm() {
  return (
    <form action={async (formData) => {
      await submitComment(formData);
    }}>
      <textarea name="comment" />
      <SubmissionStatus />
      <SubmitButton />
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Example 2: Action History

function ActionHistory() {
  const state = useActionState();

  return (
    <div>
      <h3>Recent Actions</h3>
      <ul>
        {state.history.map((action, index) => (
          <li key={index}>
            {action.type} - {action.timestamp}
            {action.status === 'error' && ` (Failed: ${action.error.message})`}
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Example 3: Retry Mechanism

function RetryableAction() {
  const state = useActionState();

  return (
    <div>
      {state.status === 'error' && (
        <button 
          onClick={() => state.retry()}
          disabled={state.retrying}
        >
          {state.retrying ? 'Retrying...' : 'Retry'}
        </button>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Example 4: Action Queue

function ActionQueue() {
  const state = useActionState();

  return (
    <div>
      <h3>Pending Actions</h3>
      {state.queue.map((action, index) => (
        <div key={index}>
          {action.type} - Queued at {action.queuedAt}
          <button onClick={() => action.cancel()}>Cancel</button>
        </div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Example 5: Action Statistics

function ActionStats() {
  const state = useActionState();

  return (
    <div>
      <h3>Action Statistics</h3>
      <p>Success Rate: {state.stats.successRate}%</p>
      <p>Average Duration: {state.stats.avgDuration}ms</p>
      <p>Total Actions: {state.stats.total}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

useOptimistic: Smooth UI Updates

The useOptimistic hook enables immediate UI updates while waiting for server responses, creating a more responsive user experience.

Example 1: Optimistic Todo List

function TodoList() {
  const [todos, setTodos] = useState([]);
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, newTodo) => [...state, newTodo]
  );

  async function addTodo(formData) {
    const newTodo = {
      id: Date.now(),
      text: formData.get('todo'),
      completed: false
    };

    addOptimisticTodo(newTodo);
    await saveTodo(newTodo);
  }

  return (
    <div>
      <form action={addTodo}>
        <input name="todo" />
        <button type="submit">Add Todo</button>
      </form>
      <ul>
        {optimisticTodos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Example 2: Optimistic Like Button

function LikeButton({ postId, initialLikes }) {
  const [likes, setLikes] = useState(initialLikes);
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    likes,
    (state) => state + 1
  );

  async function handleLike() {
    addOptimisticLike();
    await likePost(postId);
  }

  return (
    <button onClick={handleLike}>
      {optimisticLikes} Likes
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Example 3: Optimistic Comment Thread

function CommentThread({ postId }) {
  const [comments, setComments] = useState([]);
  const [optimisticComments, addOptimisticComment] = useOptimistic(
    comments,
    (state, newComment) => [...state, newComment]
  );

  async function submitComment(formData) {
    const comment = {
      id: Date.now(),
      text: formData.get('comment'),
      pending: true
    };

    addOptimisticComment(comment);
    await saveComment(postId, comment);
  }

  return (
    <div>
      <form action={submitComment}>
        <textarea name="comment" />
        <button type="submit">Comment</button>
      </form>
      {optimisticComments.map(comment => (
        <div key={comment.id} style={{ opacity: comment.pending ? 0.5 : 1 }}>
          {comment.text}
        </div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Example 4: Optimistic Shopping Cart

function ShoppingCart() {
  const [cart, setCart] = useState([]);
  const [optimisticCart, updateOptimisticCart] = useOptimistic(
    cart,
    (state, update) => {
      const { type, item } = update;
      switch (type) {
        case 'add':
          return [...state, item];
        case 'remove':
          return state.filter(i => i.id !== item.id);
        case 'update':
          return state.map(i => i.id === item.id ? item : i);
        default:
          return state;
      }
    }
  );

  async function updateCart(type, item) {
    updateOptimisticCart({ type, item });
    await saveCart({ type, item });
  }

  return (
    <div>
      {optimisticCart.map(item => (
        <div key={item.id}>
          {item.name} - ${item.price}
          <button onClick={() => updateCart('remove', item)}>Remove</button>
        </div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Example 5: Optimistic User Settings

function UserSettings() {
  const [settings, setSettings] = useState({});
  const [optimisticSettings, updateOptimisticSetting] = useOptimistic(
    settings,
    (state, update) => ({
      ...state,
      [update.key]: update.value
    })
  );

  async function updateSetting(key, value) {
    updateOptimisticSetting({ key, value });
    await saveSettings({ [key]: value });
  }

  return (
    <div>
      <div>
        <label>
          Theme:
          <select 
            value={optimisticSettings.theme} 
            onChange={e => updateSetting('theme', e.target.value)}
          >
            <option value="light">Light</option>
            <option value="dark">Dark</option>
          </select>
        </label>
      </div>
      <div>
        <label>
          Notifications:
          <input 
            type="checkbox"
            checked={optimisticSettings.notifications}
            onChange={e => updateSetting('notifications', e.target.checked)}
          />
        </label>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Remember to check the official React documentation for the most up-to-date information and best practices when using these hooks in your applications.

Happy Coding!

Comments 0 total

    Add comment