DEV Community

Mohamed Idris
Mohamed Idris

Posted on

React Router: Loaders, Actions & Form

Loader — Fetch Data Before Rendering

A loader runs before a route's component renders. This means the data is ready by the time the component mounts — no useEffect + useState combo needed.

Define the loader

// pages/Landing.jsx
import axios from 'axios';
import { useLoaderData } from 'react-router-dom';

const url = 'https://www.thecocktaildb.com/api/json/v1/1/search.php?s=margarita';

export const loader = async () => {
  const response = await axios.get(url);
  return { drinks: response.data.drinks || [] };
};
Enter fullscreen mode Exit fullscreen mode

Register it in the router

// App.jsx
import { loader as landingLoader } from './pages/Landing';

const router = createBrowserRouter([
  {
    path: '/',
    element: <Landing />,
    loader: landingLoader,
  },
]);
Enter fullscreen mode Exit fullscreen mode

Use the data in the component

const Landing = () => {
  const { drinks } = useLoaderData();

  return (
    <ul>
      {drinks.map((drink) => (
        <li key={drink.idDrink}>{drink.strDrink}</li>
      ))}
    </ul>
  );
};
Enter fullscreen mode Exit fullscreen mode

No loading state, no useEffect — the component only renders once the data is available.


Action — Handle Form Submissions

An action handles POST/PUT/PATCH/DELETE requests on a route. It replaces the traditional handleSubmit + e.preventDefault() pattern.

The old way (manual handleSubmit)

const Newsletter = () => {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await axios.post('/api/newsletter', { name, email });
      // handle success
    } catch (error) {
      // handle error
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      <button type="submit">Submit</button>
    </form>
  );
};
Enter fullscreen mode Exit fullscreen mode

The new way (React Router action + Form)

1. Define the action

// pages/Newsletter.jsx
import { Form, redirect } from 'react-router-dom';
import axios from 'axios';

export const action = async ({ request }) => {
  const formData = await request.formData();
  const data = Object.fromEntries(formData);
  // data = { name: '...', email: '...' }

  await axios.post('/api/newsletter', data);
  return redirect('/');
};
Enter fullscreen mode Exit fullscreen mode

2. Register it in the router

import { action as newsletterAction } from './pages/Newsletter';

const router = createBrowserRouter([
  {
    path: '/newsletter',
    element: <Newsletter />,
    action: newsletterAction,
  },
]);
Enter fullscreen mode Exit fullscreen mode

3. Use Form instead of form

const Newsletter = () => {
  return (
    <Form method="POST">
      <input type="text" name="name" required />
      <input type="email" name="email" required />
      <button type="submit">Submit</button>
    </Form>
  );
};
Enter fullscreen mode Exit fullscreen mode

That's it. No useState, no onChange, no handleSubmit, no e.preventDefault(). React Router's Form component intercepts the submission and sends it to the route's action.


Submission State with useNavigation

You can track whether a form is currently submitting:

import { Form, useNavigation } from 'react-router-dom';

const Newsletter = () => {
  const navigation = useNavigation();
  const isSubmitting = navigation.state === 'submitting';

  return (
    <Form method="POST">
      <input type="text" name="name" required />
      <input type="email" name="email" required />
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Submitting...' : 'Submit'}
      </button>
    </Form>
  );
};
Enter fullscreen mode Exit fullscreen mode

Quick Summary

Concept Purpose Replaces
loader Fetch data before a page renders useEffect + useState for fetching
action Handle form submissions (POST, etc.) handleSubmit + e.preventDefault()
Form React Router's form component that triggers the action Regular <form> with onSubmit
useLoaderData Access data returned by the loader State variables holding fetched data
useNavigation Track loading/submitting state Manual loading state with useState

Credits: John Smilga and Claude AI

Top comments (0)