<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged react-router</title>
<link>https://coderlegion.com/tag/react-router</link>
<description>Powered by Question2Answer</description>
<item>
<title>The easiest way to test React Router v7 apps</title>
<link>https://coderlegion.com/20647/the-easiest-way-to-test-react-router-v7-apps</link>
<description>&lt;p&gt;You wrote a React Router v7 route. It has a &lt;code&gt;loader&lt;/code&gt;, an &lt;code&gt;action&lt;/code&gt;, a form, some interactivity. Now you want to test it. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://reactrouter.com/start/framework/testing&quot;&gt;official docs&lt;/a&gt; hand you two paths and neither one covers the case you actually have.&lt;/p&gt;
&lt;p&gt;Path one is &lt;code&gt;createRoutesStub&lt;/code&gt; plus &lt;code&gt;@testing-library/react&lt;/code&gt;, running in a terminal-based test runner. It works for small reusable components that consume router hooks. The docs explicitly warn against using it for full route components with the framework-mode &lt;code&gt;Route.*&lt;/code&gt; types: &quot;not designed for (and is arguably incompatible with) direct testing of Route components&quot;. So if you want to test the actual route the user is going to load, this path stops short.&lt;/p&gt;
&lt;p&gt;Path two is Playwright or Cypress. The docs themselves point you there for route-level tests: &quot;we recommend you do that via an Integration/E2E test... against a running application&quot;. That works, but you've left your dev loop, you've added a separate test stack, and the feedback cycle gets long. It's QA-shaped, not developer-shaped.&lt;/p&gt;
&lt;p&gt;There's a middle path. Use the same &lt;code&gt;createRoutesStub&lt;/code&gt; the docs recommend for components, but render it inside the &lt;strong&gt;real browser&lt;/strong&gt;, in the &lt;strong&gt;real dev server&lt;/strong&gt;, against the &lt;strong&gt;real DOM&lt;/strong&gt;, with the dev loop still attached. That's what &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/twd-js&quot;&gt;TWD&lt;/a&gt; does. The framework's own utility, in the right environment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x6lb49bn5m362y8g0197.png&quot; alt=&quot;TWD sidebar running tests against a React Router page&quot;&gt;&lt;/p&gt;
&lt;p&gt;That's the TWD sidebar on the left, the app on the right. The sidebar is running real &lt;code&gt;createRoutesStub&lt;/code&gt; tests against the live page. No jsdom, no headless Chrome, no separate process.&lt;/p&gt;
&lt;h3 id=&quot;setup&quot;&gt;Setup&lt;/h3&gt;
&lt;p&gt;Install:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install --save-dev twd-js
npx twd-js init public --save
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the Vite plugin:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// vite.config.ts
import { reactRouter } from '@react-router/dev/vite'
import { defineConfig } from 'vite'
import { twd } from 'twd-js/vite-plugin'

export default defineConfig({
  plugins: [
    reactRouter(),
    twd({ testFilePattern: '/**/*.twd.test.{ts,tsx}' }),
  ],
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There's one SSR-mode workaround you'll hit exactly once. React Router v7 framework mode does not use a Vite-served &lt;code&gt;index.html&lt;/code&gt;. The framework's own middleware renders HTML from &lt;code&gt;app/root.tsx&lt;/code&gt;, and Vite's &lt;code&gt;transformIndexHtml&lt;/code&gt; hook never fires for that HTML. The plugin can't auto-inject its sidebar bootstrap script. The fix is one dev-only block in &lt;code&gt;app/root.tsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;head&amp;gt;
  &amp;lt;Meta /&amp;gt;
  &amp;lt;Links /&amp;gt;
  {import.meta.env.DEV &amp;amp;&amp;amp; (
    &amp;lt;script type=&quot;module&quot; src=&quot;/@id/virtual:twd/init&quot; /&amp;gt;
  )}
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The plugin still registers the virtual module. Vite still serves it at &lt;code&gt;/@id/...&lt;/code&gt;. You're just pointing your SSR'd template at it yourself. The &lt;code&gt;import.meta.env.DEV&lt;/code&gt; guard keeps the tag out of production.&lt;/p&gt;
&lt;p&gt;Run &lt;code&gt;npm run dev&lt;/code&gt;. Sidebar on the left, your app on the right.&lt;/p&gt;
&lt;h3 id=&quot;a-fallback-testing-route&quot;&gt;A fallback &lt;code&gt;/testing&lt;/code&gt; route&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;createRoutesStub&lt;/code&gt; builds a tiny in-memory router. To mount it, you need a DOM container and a React root. The cleanest way is a dedicated route that exists only as a mounting point for tests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/routes/testing-page.tsx
export default function TestPage() {
  return &amp;lt;div data-testid=&quot;testing-page&quot; style={{ minHeight: '100vh' }} /&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add it to your route table at &lt;code&gt;/testing&lt;/code&gt;. Then a small utility navigates to it and hands you a fresh root in &lt;code&gt;beforeEach&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// app/twd-tests/utils.ts
import { createRoot } from 'react-dom/client'
import { twd, screenDomGlobal } from 'twd-js'

let root: ReturnType&amp;lt;typeof createRoot&amp;gt; | undefined

export async function setupReactRoot() {
  if (root) { root.unmount(); root = undefined }
  await twd.visit('/testing')
  const container = await screenDomGlobal.findByTestId('testing-page')
  root = createRoot(container)
  return root
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's the whole harness. One route, seven lines of utility. The cost is small and you reuse it across every test.&lt;/p&gt;
&lt;h3 id=&quot;writing-a-route-level-test&quot;&gt;Writing a route-level test&lt;/h3&gt;
&lt;p&gt;This is the test the official docs say not to write with &lt;code&gt;createRoutesStub&lt;/code&gt;. In TWD it works, because the stub is rendering into the same real document your app lives in. Full &lt;code&gt;Route.*&lt;/code&gt; types, full hooks, real DOM.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/twd-tests/todoList.twd.test.tsx
import { twd, expect, userEvent, screenDom } from 'twd-js'
import { describe, it, beforeEach } from 'twd-js/runner'
import {
  createRoutesStub,
  useLoaderData,
  useParams,
  useMatches,
} from 'react-router'
import TodoListPage from '~/routes/todolist'
import todoListMock from './mocks/todoList.json'
import { setupReactRoot } from './utils'

describe('Todo List page', () =&amp;gt; {
  let root: Awaited&amp;lt;ReturnType&amp;lt;typeof setupReactRoot&amp;gt;&amp;gt;

  beforeEach(async () =&amp;gt; {
    root = await setupReactRoot()
  })

  it('renders todos from the loader', async () =&amp;gt; {
    const Stub = createRoutesStub([
      {
        path: '/',
        Component: () =&amp;gt; {
          const loaderData = useLoaderData()
          const params = useParams()
          const matches = useMatches() as any
          return (
            &amp;lt;TodoListPage
              loaderData={loaderData}
              params={params}
              matches={matches}
            /&amp;gt;
          )
        },
        loader() {
          return { todos: todoListMock }
        },
      },
    ])

    root.render(&amp;lt;Stub /&amp;gt;)
    await twd.wait(300)

    const firstTodoTitle = await screenDom.getByText('Learn TWD')
    twd.should(firstTodoTitle, 'be.visible')

    const firstTodoDate = await screenDom.getByText('Date: 2024-12-20')
    twd.should(firstTodoDate, 'be.visible')
  })

  it('submits the create-todo action with the right payload', async () =&amp;gt; {
    let payload: Record&amp;lt;string, string&amp;gt; | null = null
    const Stub = createRoutesStub([
      {
        path: '/todos',
        Component: /* same wrapper */ () =&amp;gt; null,
        loader() { return { todos: [] } },
        async action({ request }) {
          const formData = await request.formData()
          payload = Object.fromEntries(formData) as Record&amp;lt;string, string&amp;gt;
          return null
        },
      },
    ])

    root.render(&amp;lt;Stub initialEntries={['/todos']} /&amp;gt;)

    const titleInput = await screenDom.getByLabelText('Title')
    await userEvent.type(titleInput, 'Test Todo')

    const submitButton = await screenDom.getByRole('button', { name: 'Create Todo' })
    await userEvent.click(submitButton)

    expect(payload).to.deep.equal({ title: 'Test Todo' /* ... */ })
  })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The pattern is exactly the one the official docs show. The wrapper component pulls &lt;code&gt;useLoaderData&lt;/code&gt; / &lt;code&gt;useParams&lt;/code&gt; / &lt;code&gt;useMatches&lt;/code&gt; and forwards them as props. The stub provides a loader and (optionally) an action. You render, you interact, you assert. The only difference from the docs example is where it runs.&lt;/p&gt;
&lt;h3 id=&quot;backend-tests-any-runner-works&quot;&gt;Backend tests: any runner works&lt;/h3&gt;
&lt;p&gt;Framework mode splits the route in two: the &lt;code&gt;loader&lt;/code&gt; and &lt;code&gt;action&lt;/code&gt; are server-side, the component is client-side. The two halves get tested by different tools, and the backend half is the easier one.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;loader&lt;/code&gt; and &lt;code&gt;action&lt;/code&gt; are async functions that take a &lt;code&gt;Request&lt;/code&gt; and a context. They have no DOM, no router, no rendering. Test them with whatever your project already uses: vitest, jest, node's built-in test runner, anything. Here it is in vitest as one example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// app/routes/todolist.test.ts
import { describe, it, expect, vi } from 'vitest'
import { loader, action } from './todolist'
import * as api from '~/api/todos'

it('loader returns todos from the api layer', async () =&amp;gt; {
  vi.spyOn(api, 'fetchTodos').mockResolvedValue([
    { id: '1', title: 'Learn TWD', description: '', date: '2024-12-20' },
  ])

  const result = await loader({
    request: new Request('http://test/todos'),
    params: {},
    context: new Map() as any,
  })

  expect(result.todos).toHaveLength(1)
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whatever runner you pick, the test boots in milliseconds and runs anywhere Node runs. It's a function call.&lt;/p&gt;
&lt;p&gt;Two suites, one per layer. When the backend test fails, the loader broke. When the TWD test fails, the rendering broke. You stop debugging the wrong layer.&lt;/p&gt;
&lt;h3 id=&quot;why-this-works-same-utility-different-environment&quot;&gt;Why this works: same utility, different environment&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;createRoutesStub&lt;/code&gt; is a great piece of API. The official docs scope it to non-route components for a fair reason: mounting a full route component into jsdom is genuinely brittle, because framework-mode &lt;code&gt;Route.*&lt;/code&gt; types, auto-imports, and the dev loop weren't built for a synthetic DOM. You can spend more time wrestling the harness than writing the test.&lt;/p&gt;
&lt;p&gt;TWD changes one thing about that picture. The test runs inside your real Vite dev server, in the real browser tab where your app already runs. Auto-imports resolve. Types match runtime. The dev loop is still there. With the surrounding environment in place, the same &lt;code&gt;createRoutesStub&lt;/code&gt; scales up to route-level components without fighting anything.&lt;/p&gt;
&lt;p&gt;That change pulls a few extras along with it. The sidebar is part of your dev loop, not a separate thing you switch into. The same tab where you're building the page runs the tests. The DOM you're testing is the DOM you're using. Click around manually, run a test, watch the assertion render in real time, fix it, re-run. No context switch, no second window, no headless screenshot to interpret.&lt;/p&gt;
&lt;h3 id=&quot;try-it&quot;&gt;Try it&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Sample repo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/BRIKEV/twd-react-router&quot;&gt;BRIKEV/twd-react-router&lt;/a&gt;. React Router v7 in framework mode, a todo list with loader and action, the harness route wired up, GitHub Actions CI configured against a json-server backend (the part most sample repos forget).&lt;/li&gt;
&lt;li&gt;Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://twd.dev&quot;&gt;twd.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install --save-dev twd-js
npx twd-js init public --save
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the plugin to &lt;code&gt;vite.config.ts&lt;/code&gt;, drop the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag into &lt;code&gt;app/root.tsx&lt;/code&gt;, add the &lt;code&gt;/testing&lt;/code&gt; route, write your first &lt;code&gt;.twd.test.tsx&lt;/code&gt;. The sidebar opens itself.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20647/the-easiest-way-to-test-react-router-v7-apps</guid>
<pubDate>Mon, 15 Jun 2026 16:50:54 +0000</pubDate>
</item>
<item>
<title>React Router Data Mode: Part 6 - Actions, forms and mutations</title>
<link>https://coderlegion.com/4300/react-router-data-mode-part-6-actions-forms-and-mutations</link>
<description>&lt;p&gt;We continue with the sixth part of this series on React Router Data Mode. This time, we'll talk about &lt;strong&gt;actions&lt;/strong&gt;: one of the key pieces for data handling that React Router offers.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you're coming from the &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/4297/react-router-data-mode-part-5-refactor-useparams-and-navlink&quot;&gt;previous post&lt;/a&gt;, you can continue with your project as is. But if you prefer to start fresh or make sure you're at the exact point, run the following commands:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Repository link https://github.com/kevinccbsg/react-router-tutorial-devto
git reset --hard
git clean -d -f
git checkout 05-action-create-contact
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;what-is-an-action-&quot;&gt;What is an action?&lt;/h3&gt;
&lt;p&gt;An &lt;strong&gt;action&lt;/strong&gt; is a function that we can associate with a route, and it's responsible for performing &lt;strong&gt;data mutations&lt;/strong&gt;. The interesting part is that, once completed, &lt;strong&gt;React Router automatically revalidates the UI data&lt;/strong&gt; without us having to handle that process manually.&lt;/p&gt;
&lt;p&gt;In our contacts project, we'll start by creating a new contact using an action. You'll see that when you do this, the list updates automatically without needing to write extra logic.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;handling-forms&quot;&gt;Handling forms&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;src/pages/ContactForm.tsx&lt;/code&gt;, we replace the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tag with the &lt;code&gt;&amp;lt;Form&amp;gt;&lt;/code&gt; component from &lt;code&gt;react-router&lt;/code&gt;, which is responsible for triggering the corresponding action on submit.&lt;/p&gt;
&lt;p&gt;It's also important to specify the method (for example, &lt;code&gt;POST&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;Form className=&quot;space-y-4&quot; method=&quot;POST&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This component will execute the action defined in the corresponding route. To do this, we configure the action in &lt;code&gt;src/AppRoutes.tsx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;An example taken from the official documentation would be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { createBrowserRouter } from &quot;react-router&quot;;
import { someApi } from &quot;./api&quot;;

let router = createBrowserRouter([
  {
    path: &quot;/projects/:projectId&quot;,
    Component: Project,
    action: async ({ request }) =&amp;gt; {
      let formData = await request.formData();
      let title = formData.get(&quot;title&quot;);
      let project = await someApi.updateProject({ title });
      return project;
    },
  },
]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just like we did with loaders, it's recommended to extract the action to a separate file.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;src/pages/actions.tsx&lt;/code&gt; with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { ActionFunctionArgs } from &quot;react-router&quot;;

export const newContactAction = async ({ request }: ActionFunctionArgs) =&amp;gt; {
  console.log('Calling the action');
  return null;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function receives an object with &lt;code&gt;ActionFunctionArgs&lt;/code&gt;. We'll focus on &lt;code&gt;request&lt;/code&gt;, although you can also access the route's &lt;code&gt;params&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: an action must always return something, either null or a redirect (which we'll see later).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In &lt;code&gt;src/AppRoutes.tsx&lt;/code&gt;, configure the action in the &lt;code&gt;contacts/new&lt;/code&gt; route like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;{
  path: &quot;contacts/new&quot;,
  action: newContactAction,
  Component: ContactForm,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you test the form on the web, you should see the &lt;code&gt;console.log&lt;/code&gt; in your console, indicating that the action executed correctly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;getting-form-data&quot;&gt;Getting form data&lt;/h3&gt;
&lt;p&gt;We use &lt;code&gt;request.formData()&lt;/code&gt; to capture the data, since our inputs are already properly labeled with &lt;code&gt;name&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const formData = await request.formData();
const newContact = {
  firstName: formData.get('firstName') as string,
  lastName: formData.get('lastName') as string,
  username: formData.get('username') as string,
  email: formData.get('email') as string,
  phone: formData.get('phone') as string,
  avatar: formData.get('avatar') as string || undefined,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, we validate that the method is &lt;code&gt;POST&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const method = request.method.toUpperCase();
if (method === 'POST') {
  // create the contact
}
return null;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-action&quot;&gt;Final action&lt;/h3&gt;
&lt;p&gt;The complete code for our action would look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { ActionFunctionArgs } from &quot;react-router&quot;;
import { createContact } from &quot;@/api/contacts&quot;;

interface NewContact {
  firstName: string;
  lastName: string;
  username: string;
  email: string;
  phone: string;
  avatar?: string;
}

export const newContactAction = async ({ request }: ActionFunctionArgs) =&amp;gt; {
  const formData = await request.formData();
  const method = request.method.toUpperCase();
  
  if (method === 'POST') {
    const newContact: NewContact = {
      firstName: formData.get('firstName') as string,
      lastName: formData.get('lastName') as string,
      username: formData.get('username') as string,
      email: formData.get('email') as string,
      phone: formData.get('phone') as string,
      avatar: formData.get('avatar') as string || undefined,
    };
    await createContact(newContact);
  }

  return null;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you create a new contact, it will automatically appear in the sidebar.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;redirect-after-creation&quot;&gt;Redirect after creation&lt;/h3&gt;
&lt;p&gt;Now let's improve the experience by redirecting directly to the new contact's page. We use the &lt;code&gt;redirect&lt;/code&gt; helper from &lt;code&gt;react-router&lt;/code&gt; right after creating the contact:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { ActionFunctionArgs, redirect } from &quot;react-router&quot;;
import { createContact } from &quot;@/api/contacts&quot;;

interface NewContact {
  firstName: string;
  lastName: string;
  username: string;
  email: string;
  phone: string;
  avatar?: string;
}

export const newContactAction = async ({ request }: ActionFunctionArgs) =&amp;gt; {
  const formData = await request.formData();
  const method = request.method.toUpperCase();
  
  if (method === 'POST') {
    const newContact: NewContact = {
      firstName: formData.get('firstName') as string,
      lastName: formData.get('lastName') as string,
      username: formData.get('username') as string,
      email: formData.get('email') as string,
      phone: formData.get('phone') as string,
      avatar: formData.get('avatar') as string || undefined,
    };
    const newContactResponse = await createContact(newContact);
    return redirect(`/contacts/${newContactResponse.id}`);
  }

  return null;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;handling-loading-state&quot;&gt;Handling loading state&lt;/h3&gt;
&lt;p&gt;To manage the form state while it's submitting or loading, we use the &lt;code&gt;useNavigation&lt;/code&gt; hook provided by React Router. This hook gives us information about the current navigation, and we use it to know if we're submitting the form or waiting for the UI to update after the &lt;code&gt;redirect&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const navigation = useNavigation();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this, we have access to the current navigation state, which can be &lt;code&gt;&quot;idle&quot;&lt;/code&gt;, &lt;code&gt;&quot;submitting&quot;&lt;/code&gt;, or &lt;code&gt;&quot;loading&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then we create two variables to detect if we're submitting this particular form (not any other) or if we're waiting for the view to reload after completion:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const isSubmitting = navigation.state === 'submitting' &amp;amp;&amp;amp; navigation.formAction === '/contacts/new';
const isLoading = navigation.state === 'loading' &amp;amp;&amp;amp; navigation.formAction === '/contacts/new';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is important because there may be other routes or forms in the app, and we don't want to disable the button if it's not this form being used.&lt;/p&gt;
&lt;p&gt;Finally, we combine both states to know if the button should be disabled:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const disabled = isSubmitting || isLoading;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, we prevent the user from submitting multiple times while the contact is being created or the UI is updating.&lt;/p&gt;
&lt;p&gt;Here's the result:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Form, useNavigation } from 'react-router';

const ContactForm = () =&amp;gt; {
  const navigation = useNavigation();
  const isSubmitting = navigation.state === 'submitting' &amp;amp;&amp;amp; navigation.formAction === '/contacts/new';
  const isLoading = navigation.state === 'loading' &amp;amp;&amp;amp; navigation.formAction === '/contacts/new';
  const disabled = isSubmitting || isLoading;
  return (
    &amp;lt;div className=&quot;max-w-md mx-auto&quot;&amp;gt;
      &amp;lt;h1 className=&quot;text-2xl font-bold mb-4&quot;&amp;gt;Create New Contact&amp;lt;/h1&amp;gt;
      &amp;lt;Form className=&quot;space-y-4&quot; method=&quot;POST&quot;&amp;gt;
        {/* your fields */}
        &amp;lt;Button type=&quot;submit&quot; disabled={disabled }&amp;gt;
          {disabled ? 'Creating...' : 'Create Contact'}
        &amp;lt;/Button&amp;gt;
      &amp;lt;/Form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default ContactForm;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this, we achieve a better UX and prevent unnecessary multiple submits.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;That's it for this sixth part, where we've created an action, handled the form, redirected after submit, and managed loading state.&lt;/p&gt;
&lt;p&gt;You might be wondering: what about validation? What if I have multiple actions on the same page, like editing or deleting contacts?&lt;/p&gt;
&lt;p&gt;We'll cover all that in the next part: &lt;strong&gt;how to handle multiple actions, validations, and actions that don't depend on a&lt;/strong&gt; &lt;code&gt;&amp;lt;Form&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;See you in the next one!&lt;/p&gt;
</description>
<category>React Router</category>
<guid isPermaLink="true">https://coderlegion.com/4300/react-router-data-mode-part-6-actions-forms-and-mutations</guid>
<pubDate>Mon, 28 Jul 2025 20:31:27 +0000</pubDate>
</item>
<item>
<title>React Router Data Mode: Part 5 - Refactor, useParams and NavLink</title>
<link>https://coderlegion.com/4297/react-router-data-mode-part-5-refactor-useparams-and-navlink</link>
<description>&lt;p&gt;We continue with the &lt;strong&gt;fifth part&lt;/strong&gt; of this series on React Router Data Mode. This time, it will be a brief post where we’ll do some refactors, review the &lt;code&gt;useParams&lt;/code&gt; hook, and improve navigation with &lt;code&gt;NavLink&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you’re coming from the &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/4233/react-router-data-mode-part-4-routes-with-parameters-userouteloaderdata-and-useparams&quot;&gt;previous post&lt;/a&gt;, you can continue with your project as is. But if you prefer to start fresh or make sure you’re at the exact point, run the following commands:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Repository link https://github.com/kevinccbsg/react-router-tutorial-devto
git reset --hard
git clean -d -f
git checkout 04-refactor-sidebar-detail
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;refactor&quot;&gt;Refactor&lt;/h3&gt;
&lt;p&gt;Let’s start by improving the detail view.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;src/components/ContactCard/ContactCard.tsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { Form } from &quot;react-router&quot;;
import { Star, StarOff } from &quot;lucide-react&quot;;
import { Button } from &quot;@/components/ui/button&quot;;
import { Avatar, AvatarFallback, AvatarImage } from &quot;@/components/ui/avatar&quot;;
import { Card, CardContent } from &quot;@/components/ui/card&quot;;

interface Contact {
  id: string;
  name: string;
  username: string;
  favorite: boolean;
  avatar?: string;
}

export default function ContactCard({ avatar, name, username, favorite, id }: Contact) {
  return (
    &amp;lt;Card className=&quot;max-w-md mx-auto&quot;&amp;gt;
      &amp;lt;CardContent className=&quot;flex flex-col items-center gap-4 p-6&quot;&amp;gt;
        &amp;lt;Avatar className=&quot;w-32 h-32&quot;&amp;gt;
          &amp;lt;AvatarImage src={avatar || undefined} /&amp;gt;
          &amp;lt;AvatarFallback&amp;gt;{name[0]}&amp;lt;/AvatarFallback&amp;gt;
        &amp;lt;/Avatar&amp;gt;
        &amp;lt;div className=&quot;text-center&quot;&amp;gt;
          &amp;lt;h2 className=&quot;text-xl font-bold&quot;&amp;gt;{name}&amp;lt;/h2&amp;gt;
          {username &amp;amp;&amp;amp; (
            &amp;lt;p className=&quot;text-sm text-muted-foreground&quot;&amp;gt;{username}&amp;lt;/p&amp;gt;
          )}
        &amp;lt;/div&amp;gt;
        &amp;lt;div className=&quot;flex gap-2&quot;&amp;gt;
          &amp;lt;Form method=&quot;DELETE&quot;&amp;gt;
            &amp;lt;input type=&quot;hidden&quot; name=&quot;id&quot; value={id} /&amp;gt;
            &amp;lt;Button type=&quot;submit&quot; variant=&quot;destructive&quot;&amp;gt;Delete&amp;lt;/Button&amp;gt;
          &amp;lt;/Form&amp;gt;
          &amp;lt;Form method=&quot;PATCH&quot;&amp;gt;
            &amp;lt;input type=&quot;hidden&quot; name=&quot;id&quot; value={id} /&amp;gt;
            &amp;lt;input type=&quot;hidden&quot; name=&quot;favorite&quot; value={String(!favorite)} /&amp;gt;
            &amp;lt;Button type=&quot;submit&quot; variant=&quot;ghost&quot;&amp;gt;
              {favorite ? &amp;lt;Star className=&quot;w-4 h-4&quot; /&amp;gt; : &amp;lt;StarOff className=&quot;w-4 h-4&quot; /&amp;gt;}
            &amp;lt;/Button&amp;gt;
          &amp;lt;/Form&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/CardContent&amp;gt;
    &amp;lt;/Card&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then update the detail page &lt;code&gt;src/pages/ContactDetail.tsx&lt;/code&gt; to use this new component:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { useParams, useRouteLoaderData } from &quot;react-router&quot;;
import { loadContacts } from &quot;./loader&quot;;
import ContactCard from &quot;@/components/ContactCard/ContactCard&quot;;

const ContactDetail = () =&amp;gt; {
  const { contactId } = useParams&amp;lt;{ contactId: string }&amp;gt;(); // Needs TS type annotation
  const routeData = useRouteLoaderData&amp;lt;typeof loadContacts&amp;gt;(&quot;root&quot;);
  if (!routeData) {
    return &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;;
  }

  const { contacts } = routeData;
  
  // Find the contact locally (outside the store)
  const contact = contacts.find((c) =&amp;gt; c.id === contactId);

  if (!contact) {
    return &amp;lt;div&amp;gt;Contact not found&amp;lt;/div&amp;gt;;
  }
  return (
    &amp;lt;ContactCard
      avatar={contact.avatar}
      name={`${contact.firstName} ${contact.lastName}`}
      username={contact.username}
      favorite={contact.favorite}
      id={contact.id}
    /&amp;gt;
  );
}

export default ContactDetail;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now create the component &lt;code&gt;src/components/Sidebar/Sidebar.tsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { Input } from &quot;@/components/ui/input&quot;
import { Button } from &quot;@/components/ui/button&quot;
import { ScrollArea } from &quot;@/components/ui/scroll-area&quot;
import { Link } from &quot;react-router&quot;
import { useState } from &quot;react&quot;;

interface Contact {
  id: string;
  name: string;
}

export default function Sidebar({ contacts }: { contacts: Contact[] }) {
  const [search, setSearch] = useState(&quot;&quot;);

  const handlesearchChange = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
    setSearch(e.target.value);
  };

  const filteredContacts = contacts.filter(contact =&amp;gt;
    contact.name.toLowerCase().includes(search.toLowerCase())
  );

  return (
    &amp;lt;&amp;gt;
      &amp;lt;Input placeholder=&quot;Search...&quot; className=&quot;mb-2&quot; value={search} onChange={handlesearchChange} /&amp;gt;
      &amp;lt;Button className=&quot;w-full&quot; variant=&quot;secondary&quot; asChild&amp;gt;
        &amp;lt;Link to=&quot;/contacts/new&quot; viewTransition&amp;gt;
          New
        &amp;lt;/Link&amp;gt;
      &amp;lt;/Button&amp;gt;
      &amp;lt;ScrollArea className=&quot;flex-1&quot;&amp;gt;
        &amp;lt;div className=&quot;flex flex-col gap-1 mt-4&quot;&amp;gt;
          {filteredContacts.map(contact =&amp;gt; (
            &amp;lt;Button
              key={contact.id}
              className=&quot;justify-start&quot;
              asChild
            &amp;gt;
              &amp;lt;Link to={`/contacts/${contact.id}`} viewTransition&amp;gt;
                {contact.name}
              &amp;lt;/Link&amp;gt;
            &amp;lt;/Button&amp;gt;
          ))}
        &amp;lt;/div&amp;gt;
      &amp;lt;/ScrollArea&amp;gt;
    &amp;lt;/&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This component already includes local search, although that won’t be the focus of this post.&lt;br&gt;
The important thing here is navigation.&lt;/p&gt;
&lt;p&gt;Update the main page &lt;code&gt;pages/contacts.tsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { Outlet, useLoaderData } from &quot;react-router&quot;;
import { loadContacts } from &quot;./loader&quot;;
import Sidebar from &quot;@/components/Sidebar/Sidebar&quot;;

const ContactsPage = () =&amp;gt; {
  const { contacts } = useLoaderData&amp;lt;typeof loadContacts&amp;gt;();
  return (
    &amp;lt;div className=&quot;h-screen grid grid-cols-[300px_1fr]&quot;&amp;gt;
      {/* Sidebar */}
      &amp;lt;div className=&quot;border-r p-4 flex flex-col gap-4&quot;&amp;gt;
        &amp;lt;Sidebar contacts={contacts.map(contact =&amp;gt; ({
          id: contact.id,
          name: `${contact.firstName} ${contact.lastName}`,
        }))} /&amp;gt;
      &amp;lt;/div&amp;gt;
      {/* Detail View */}
      &amp;lt;div className=&quot;p-8&quot;&amp;gt;
        &amp;lt;Outlet /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default ContactsPage;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;how-do-we-mark-the-active-link-&quot;&gt;How do we mark the active link?&lt;/h3&gt;
&lt;p&gt;To correctly indicate which contact is selected, we use the &lt;code&gt;useParams&lt;/code&gt; hook:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const { contactId } = useParams&amp;lt;{ contactId: string }&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with that, adjust the button in the contact list:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;Button
  key={contact.id}
  className=&quot;justify-start&quot;
  variant={contact.id === contactId ? &quot;default&quot; : &quot;ghost&quot;}
  asChild
&amp;gt;
  &amp;lt;Link to={`/contacts/${contact.id}`} viewTransition&amp;gt;
    {contact.name}
  &amp;lt;/Link&amp;gt;
&amp;lt;/Button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this change, the active contact is now correctly shown in the list.&lt;/p&gt;
&lt;h3 id=&quot;what-about-navlink-&quot;&gt;What about NavLink?&lt;/h3&gt;
&lt;p&gt;React Router also includes the component &lt;a rel=&quot;nofollow&quot; href=&quot;https://reactrouter.com/api/components/NavLink#props&quot;&gt;https://reactrouter.com/api/components/NavLink#props&lt;/a&gt;, which extends &lt;code&gt;Link&lt;/code&gt; with improvements for the &lt;code&gt;active&lt;/code&gt; and &lt;code&gt;pending&lt;/code&gt; states.&lt;/p&gt;
&lt;p&gt;Specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It automatically applies classes to the link when it’s active or pending.&lt;/li&gt;
&lt;li&gt;It adds the attribute &lt;code&gt;aria-current=&quot;page&quot;&lt;/code&gt; when the link represents the current route.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In our case, since we use ShadCN’s &lt;code&gt;Button&lt;/code&gt;, we don’t take advantage of NavLink’s CSS classes, but we can benefit from its accessibility support (aria-current), which is a good practice for navigations like this.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;Button
  key={contact.id}
  className=&quot;justify-start&quot;
  variant={contact.id === contactId ? &quot;default&quot; : &quot;ghost&quot;}
  asChild
&amp;gt;
  &amp;lt;NavLink to={`/contacts/${contact.id}`} viewTransition&amp;gt;
    {contact.name}
  &amp;lt;/NavLink&amp;gt;
&amp;lt;/Button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;And that’s it for this part. In the next post we’ll dive into actions, another interesting concept inherited from Remix, which will allow us to start making mutations within the app &lt;/p&gt;
</description>
<category>React Router</category>
<guid isPermaLink="true">https://coderlegion.com/4297/react-router-data-mode-part-5-refactor-useparams-and-navlink</guid>
<pubDate>Mon, 28 Jul 2025 07:18:15 +0000</pubDate>
</item>
<item>
<title>React Router Data Mode: Part 4 - Routes with parameters, useRouteLoaderData and useParams</title>
<link>https://coderlegion.com/4233/react-router-data-mode-part-4-routes-with-parameters-userouteloaderdata-and-useparams</link>
<description>&lt;p&gt;We continue with the fourth part of this series on React Router data mode. This time, we’ll dive deeper into &lt;em&gt;loaders&lt;/em&gt;, first by adding a contact detail screen and then exploring hooks like &lt;code&gt;useRouteLoaderData&lt;/code&gt; and &lt;code&gt;useParams&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you’re coming from the &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/3769/react-router-data-mode-part-3-loaders-and-data-fetching&quot;&gt;previous post&lt;/a&gt;, you can continue with your project as is. But if you prefer to start fresh or make sure you’re at the exact point, run the following commands:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Repository link https://github.com/kevinccbsg/react-router-tutorial-devto
git reset --hard
git clean -d -f
git checkout 03-loaders-detail-page
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;a-quick-recap&quot;&gt;A quick recap&lt;/h3&gt;
&lt;p&gt;So far, we only have the &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;contacts/new&lt;/code&gt; routes, but we’re missing something key: a detail page for each contact. This page will live inside the root route &lt;code&gt;/&lt;/code&gt;, so we need to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create the new page.&lt;/li&gt;
&lt;li&gt;Add a nested route with a parameter.&lt;/li&gt;
&lt;li&gt;Review our links.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;1-create-the-page&quot;&gt;1. Create the page&lt;/h4&gt;
&lt;p&gt;Create &lt;code&gt;src/pages/ContactDetail.tsx&lt;/code&gt;, for now with a very basic design. In the next part, we’ll improve the UI:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;
const contact = {
  firstName: 'John',
  username: 'john_doe',
};

const ContactDetail = () =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;Contact Detail&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;{contact.firstName}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{contact.username}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default ContactDetail;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-create-the-nested-route&quot;&gt;2. Create the nested route&lt;/h4&gt;
&lt;p&gt;We want the route to change according to the contact: &lt;code&gt;contacts/:contactId&lt;/code&gt;. We won’t use query params like &lt;code&gt;?id=123&lt;/code&gt;, because &lt;strong&gt;we’re not filtering a list&lt;/strong&gt;, but accessing an individual resource.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const AppRoutes = createBrowserRouter([
  {
    path: &quot;/&quot;,
    loader: loadContacts,
    HydrateFallback: ContactsSkeletonPage,
    Component: ContactsPage,
    children: [
      {
        path: &quot;contacts/:contactId&quot;,
        Component: ContactDetail,
      },
      {
        path: &quot;contacts/new&quot;,
        Component: ContactForm,
      },
    ],
  },
  ... // the other routes
]);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-review-our-links&quot;&gt;3. Review our links&lt;/h4&gt;
&lt;p&gt;Our links were already well defined:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;Link to={`/contacts/${contact.id}`} viewTransition&amp;gt;
  {contact.firstName} {contact.lastName}
&amp;lt;/Link&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, now we can see that when selecting each contact, the URL changes but the content remains the same. This is because our page always shows the same content. We need to add our loader.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;add-the-loader-for-the-detail&quot;&gt;Add the loader for the detail&lt;/h3&gt;
&lt;p&gt;Right now, no matter which contact we select, the same content is always shown. We need a loader to load the correct contact.&lt;/p&gt;
&lt;p&gt;Create a new method in &lt;code&gt;src/pages/loader.ts&lt;/code&gt;. This time, we’ll need to access &lt;code&gt;params&lt;/code&gt; to get the &lt;code&gt;contactId&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export const loadContactDetail = async ({ params }: LoaderFunctionArgs) =&amp;gt; {
  const contactId = params.contactId;
  /*
  Here we validate that contactId exists.
  We’ll cover 404 error handling or invalid responses in
  another post
  */
  if (!contactId) {
    throw new Error(&quot;Contact ID is required&quot;);
  }
  const contact = await fetchContactById(contactId);
  return { contact };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also update the function in &lt;code&gt;src/api/contacts.ts&lt;/code&gt; to simulate a delay:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export const fetchContactById = async (id: string) =&amp;gt; {
  const response = await api.get&amp;lt;Contact&amp;gt;(`/contacts/${id}`);
  await delay(500); // Simulate network delay
  return response.data;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, connect everything in the routes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const AppRoutes = createBrowserRouter([
  {
    path: &quot;/&quot;,
    loader: loadContacts,
    HydrateFallback: ContactsSkeletonPage,
    Component: ContactsPage,
    children: [
      {
        path: &quot;contacts/:contactId&quot;,
        loader: loadContactDetail,
        Component: ContactDetail,
      },
      {
        path: &quot;contacts/new&quot;,
        Component: ContactForm,
      },
    ],
  },
  ...
]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in the component:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { useLoaderData } from &quot;react-router&quot;;
import { loadContactDetail } from &quot;./loader&quot;;

const ContactDetail = () =&amp;gt; {
  const { contact } = useLoaderData&amp;lt;typeof loadContactDetail&amp;gt;();
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;Contact Detail&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;{contact.firstName}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{contact.username}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default ContactDetail;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this, we now have our functional detail screen using loaders. &lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;but-didn-t-we-already-have-that-data-in-another-loader-&quot;&gt;But didn’t we already have that data in another loader?&lt;/h3&gt;
&lt;p&gt;Our detail screen lives inside the main route &lt;code&gt;/&lt;/code&gt;, which already loads all contacts. Does it make sense to make another API call just to show a contact we already have?&lt;/p&gt;
&lt;p&gt;We can avoid this by using the &lt;code&gt;useRouteLoaderData&lt;/code&gt; hook.&lt;/p&gt;
&lt;h3 id=&quot;using-userouteloaderdata&quot;&gt;Using useRouteLoaderData&lt;/h3&gt;
&lt;p&gt;This hook lets you access the data from a loader. In our case, the root route (&lt;code&gt;/&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;First, give that route an &lt;code&gt;id&lt;/code&gt; and remove the loader from the detail:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const AppRoutes = createBrowserRouter([
  {
    path: &quot;/&quot;,
    loader: loadContacts,
    id: &quot;root&quot;,
    HydrateFallback: ContactsSkeletonPage,
    Component: ContactsPage,
    children: [
      {
        path: &quot;contacts/:contactId&quot;,
        Component: ContactDetail,
      },
      {
        path: &quot;contacts/new&quot;,
        Component: ContactForm,
      },
    ],
  },
  ...
]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in the component:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { useRouteLoaderData } from &quot;react-router&quot;;
import { loadContacts } from &quot;./loader&quot;;

const ContactDetail = () =&amp;gt; {
  const routeData = useRouteLoaderData&amp;lt;typeof loadContacts&amp;gt;(&quot;root&quot;);
  if (!routeData) {
    return &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;;
  }
  const contact = routeData.contacts[0]; // For simplicity, show the first one
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;Contact Detail&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;{contact.firstName}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{contact.username}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default ContactDetail;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, we use &lt;code&gt;useRouteLoaderData&lt;/code&gt; with the ID &lt;code&gt;&quot;root&quot;&lt;/code&gt; to access the data already loaded by the root route’s loader. We also add &lt;code&gt;typeof loadContacts&lt;/code&gt; so TypeScript gives us autocomplete and type checking. It’s important to validate that the data exists, since on the first load it might not be available. That’s why we show a fallback (Loading...) in the meantime.&lt;/p&gt;
&lt;h3 id=&quot;but-we-need-to-know-which-contact-to-show&quot;&gt;But we need to know which contact to show&lt;/h3&gt;
&lt;p&gt;For that, we use &lt;code&gt;useParams&lt;/code&gt; to access the &lt;code&gt;params&lt;/code&gt; defined in the URL:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { useParams, useRouteLoaderData } from &quot;react-router&quot;;
import { loadContacts } from &quot;./loader&quot;;

const ContactDetail = () =&amp;gt; {
  const { contactId } = useParams&amp;lt;{ contactId: string }&amp;gt;(); // Needs TS type annotation
  const routeData = useRouteLoaderData&amp;lt;typeof loadContacts&amp;gt;(&quot;root&quot;);
  if (!routeData) {
    return &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;;
  }

  const { contacts } = routeData;
  
  // Find the contact locally
  const contact = contacts.find(({ id }) =&amp;gt; id === contactId);

  if (!contact) {
    return &amp;lt;div&amp;gt;Contact not found&amp;lt;/div&amp;gt;;
  }
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;Contact Detail&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;{contact.firstName}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{contact.username}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default ContactDetail;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have navigation between contacts working with already loaded data, without extra requests. We also reviewed how to use &lt;code&gt;params&lt;/code&gt; in both &lt;code&gt;loaders&lt;/code&gt; and components, with proper typing.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In part 5 we’ll improve the detail design and refactor navigation, making sure the active link is correctly marked.&lt;br&gt;
See you in the next one!&lt;/p&gt;
</description>
<category>React Router</category>
<guid isPermaLink="true">https://coderlegion.com/4233/react-router-data-mode-part-4-routes-with-parameters-userouteloaderdata-and-useparams</guid>
<pubDate>Wed, 23 Jul 2025 08:30:33 +0000</pubDate>
</item>
<item>
<title>React Router Data Mode: Part 3 - Loaders and data fetching</title>
<link>https://coderlegion.com/3769/react-router-data-mode-part-3-loaders-and-data-fetching</link>
<description>&lt;p&gt;Let's move on to the third part of this tutorial series. In this case, we'll look at a concept that comes from Remix and can now also be found in React Router: &lt;strong&gt;loaders&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If you're coming from the &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/3682/react-router-data-mode-part-2-nested-routes-and-outlets&quot;&gt;previous tutorial&lt;/a&gt;, you can leave the project as is, but if you want to make sure everything is clean or start from the same point, you can run the following commands:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Repository link https://github.com/kevinccbsg/react-router-tutorial-devto
git reset --hard
git clean -d -f
git checkout 02-loaders-detail-page
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;what-are-loaders-&quot;&gt;What are loaders?&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Loaders&lt;/em&gt; are a mechanism that React Router provides to send information to our components. They are functions that we add in our route definitions.&lt;/p&gt;
&lt;p&gt;Here's an example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;createBrowserRouter([
  {
    path: &quot;/&quot;,
    loader: async () =&amp;gt; {
      // return data from here
      return { records: await getSomeRecords() };
    },
    Component: MyRoute,
  },
]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;em&gt;loaders&lt;/em&gt;, we add everything we consider necessary to load for that page. When working with React Router, we need to start thinking in terms of pages, since all data loading and mutation is organized at that level.&lt;/p&gt;
&lt;p&gt;In our contacts page, we're currently using a hardcoded array of data, which isn't realistic. So, we'll replace it with a call to an API we've created using &lt;code&gt;json-server&lt;/code&gt;.&lt;br&gt;
We won't go into detail about what &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/json-server&quot;&gt;&lt;code&gt;json-server&lt;/code&gt;&lt;/a&gt; is (it's basically a quick way to simulate a REST API with a JSON file), just note that from now on we'll test the app with the command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run serve:dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command starts both the API with &lt;code&gt;json-server&lt;/code&gt; and the frontend.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;src/api&lt;/code&gt; folder are all the methods that call the API using Axios. Since this series is about React Router, we won't go into detail about that part.&lt;/p&gt;
&lt;p&gt;Now that we've covered the workflow, let's start the refactor. Normally in React, to load data in a component, we use something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const [contacts, setContacts] = useState&amp;lt;Contact[]&amp;gt;([]);

useEffect(() =&amp;gt; {
  fetchContacts()
    .then((data) =&amp;gt; setContacts(data))
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With React Router, this changes, as we'll use a loader defined in &lt;code&gt;AppRoutes.tsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { createBrowserRouter } from &quot;react-router&quot;;
import ContactsPage from &quot;./pages/Contacts&quot;;
import ContactForm from &quot;./pages/ContactForm&quot;;
import { fetchContacts } from &quot;@/api/contacts&quot;;

const AppRoutes = createBrowserRouter([
  {
    path: &quot;/&quot;,
    // this property
    loader: async () =&amp;gt; {
      const contacts = await fetchContacts();
      return { contacts };
    },
    Component: ContactsPage,
    children: [
      {
        path: &quot;contacts/new&quot;,
        Component: ContactForm,
      },
    ],
  },
  {
    path: &quot;/about&quot;,
    element: &amp;lt;div&amp;gt;About&amp;lt;/div&amp;gt;,
  },
  {
    path: &quot;*&quot;,
    element: &amp;lt;div&amp;gt;Not Found&amp;lt;/div&amp;gt;,
  },
]);

export default AppRoutes;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Although this works, and we could then access the data using a React Router hook, &lt;strong&gt;I don't recommend doing it this way&lt;/strong&gt;. The routes file can grow a lot, and later we'll need to solve a typing issue with TypeScript, for which this approach isn't ideal.&lt;/p&gt;
&lt;p&gt;It's better to create a separate file, for example: &lt;code&gt;src/pages/loader.tsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { fetchContacts } from &quot;@/api/contacts&quot;;

export const loadContacts = async () =&amp;gt; {
  const contacts = await fetchContacts();
  return { contacts };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, the &lt;code&gt;AppRoutes.tsx&lt;/code&gt; file is much cleaner:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { createBrowserRouter } from &quot;react-router&quot;;
import ContactsPage from &quot;./pages/Contacts&quot;;
import ContactForm from &quot;./pages/ContactForm&quot;;
import { loadContacts } from &quot;./pages/loader&quot;;

const AppRoutes = createBrowserRouter([
  {
    path: &quot;/&quot;,
    loader: loadContacts,
    Component: ContactsPage,
    children: [
      {
        path: &quot;contacts/new&quot;,
        Component: ContactForm,
      },
    ],
  },
  {
    path: &quot;/about&quot;,
    element: &amp;lt;div&amp;gt;About&amp;lt;/div&amp;gt;,
  },
  {
    path: &quot;*&quot;,
    element: &amp;lt;div&amp;gt;Not Found&amp;lt;/div&amp;gt;,
  },
]);

export default AppRoutes;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we navigate to the web app, we'll see in the &quot;Network&quot; tab that the API call is made.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oho2mzpzgfzht390qyp3.png&quot; alt=&quot;network tab with the call&quot;&gt;&lt;/p&gt;
&lt;p&gt;But the data is still hardcoded in the UI. How do we retrieve the info?&lt;/p&gt;
&lt;p&gt;We use the &lt;code&gt;useLoaderData&lt;/code&gt; hook from React Router:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const { contacts } = useLoaderData();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This hook always returns what the &lt;em&gt;loader&lt;/em&gt; defined in the route for the component returns. In this case, an object with &lt;code&gt;contacts&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, if we use this in a TypeScript project, we'll get a typing error. To fix it, we write it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;const { contacts } = useLoaderData&amp;lt;typeof loadContacts&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So the &lt;code&gt;Contacts&lt;/code&gt; component ends up like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { Link, Outlet, useLoaderData } from &quot;react-router&quot;;
import { ScrollArea } from &quot;@/components/ui/scroll-area&quot;;
import { Button } from &quot;@/components/ui/button&quot;;
import { loadContacts } from &quot;./loader&quot;;

const ContactsPage = () =&amp;gt; {
  const { contacts } = useLoaderData&amp;lt;typeof loadContacts&amp;gt;();
  return (
    &amp;lt;div className=&quot;h-screen grid grid-cols-[300px_1fr]&quot;&amp;gt;
      {/* Sidebar */}
      &amp;lt;div className=&quot;border-r p-4 flex flex-col gap-4&quot;&amp;gt;
        &amp;lt;Button className=&quot;w-full&quot; variant=&quot;secondary&quot; asChild&amp;gt;
          &amp;lt;Link to=&quot;/contacts/new&quot; viewTransition&amp;gt;
            New
          &amp;lt;/Link&amp;gt;
        &amp;lt;/Button&amp;gt;
        &amp;lt;ScrollArea className=&quot;flex-1&quot;&amp;gt;
        &amp;lt;div className=&quot;flex flex-col gap-1 mt-4&quot;&amp;gt;
          {contacts.map(contact =&amp;gt; (
            &amp;lt;Button
              key={contact.id}
              className=&quot;justify-start&quot;
              asChild
            &amp;gt;
              &amp;lt;Link to={`/contacts/${contact.id}`} viewTransition&amp;gt;
                {contact.firstName} {contact.lastName}
              &amp;lt;/Link&amp;gt;
            &amp;lt;/Button&amp;gt;
          ))}
        &amp;lt;/div&amp;gt;
      &amp;lt;/ScrollArea&amp;gt;
      &amp;lt;/div&amp;gt;
      {/* Detail View */}
      &amp;lt;div className=&quot;p-8&quot;&amp;gt;
        &amp;lt;Outlet /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default ContactsPage;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's why it's more convenient to have a loader file per page, as it improves type management and keeps everything more separated.&lt;/p&gt;
&lt;p&gt;With this, we're doing the same as we did with &lt;code&gt;useEffect&lt;/code&gt;, but &lt;strong&gt;in the recommended way for React Router&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Finally, you may have noticed a warning in the terminal:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8drcsa5w1l5kuxrev63k.png&quot; alt=&quot;hydrate fallback warning&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is because we don't have a loading screen yet. We can simulate a delay in the API to see the problem. In &lt;code&gt;src/api/contacts.ts&lt;/code&gt;, uncomment the &lt;code&gt;delay&lt;/code&gt; function and modify &lt;code&gt;fetchContacts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export const fetchContacts = async () =&amp;gt; {
  const response = await api.get&amp;lt;Contact[]&amp;gt;('/contacts');
  await delay(2000); // Simulate network latency
  return response.data;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we reload, we'll see the page stays blank for a few seconds until the data loads. This is because there's no &lt;em&gt;loading state&lt;/em&gt;. React Router allows us to handle this in several ways, but in this post we'll use the &lt;code&gt;HydrateFallback&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;In this project, we already have the &lt;code&gt;ContactsSkeletonPage&lt;/code&gt; component, so we add it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { createBrowserRouter } from &quot;react-router&quot;;
import ContactsPage from &quot;./pages/Contacts&quot;;
import ContactForm from &quot;./pages/ContactForm&quot;;
import { loadContacts } from &quot;./pages/loader&quot;;
import ContactsSkeletonPage from &quot;./Layouts/HomeSkeleton&quot;;

const AppRoutes = createBrowserRouter([
  {
    path: &quot;/&quot;,
    loader: loadContacts,
    HydrateFallback: ContactsSkeletonPage,
    Component: ContactsPage,
    children: [
      {
        path: &quot;contacts/new&quot;,
        Component: ContactForm,
      },
    ],
  },
  {
    path: &quot;/about&quot;,
    element: &amp;lt;div&amp;gt;About&amp;lt;/div&amp;gt;,
  },
  {
    path: &quot;*&quot;,
    element: &amp;lt;div&amp;gt;Not Found&amp;lt;/div&amp;gt;,
  },
]);

export default AppRoutes;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with this, we now have a loading screen. The best part is that this can be applied per page, and each one loads independently without blocking the others.&lt;/p&gt;
&lt;p&gt;We'll see this in more detail in the next post about loaders.&lt;br&gt;
Without a doubt, this is one of the most important and powerful parts of React Router, and it will be even more so when we combine it with &lt;em&gt;actions&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;See you in part 4&lt;/p&gt;
</description>
<category>React Router</category>
<guid isPermaLink="true">https://coderlegion.com/3769/react-router-data-mode-part-3-loaders-and-data-fetching</guid>
<pubDate>Mon, 07 Jul 2025 11:18:14 +0000</pubDate>
</item>
<item>
<title>React Router Data Mode: Part 2 - Nested routes and outlets</title>
<link>https://coderlegion.com/3682/react-router-data-mode-part-2-nested-routes-and-outlets</link>
<description>&lt;p&gt;In this second part of our React Router v7 tutorial, we will dive deeper into the routing system: how to nest views within a shared layout, how to navigate without reloading the page using &lt;code&gt;Link&lt;/code&gt;, and how to add visual transitions for a smoother experience.&lt;/p&gt;
&lt;p&gt;If you're coming from the &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/3526/react-router-data-mode-part-1-installation-and-first-routes&quot;&gt;previous tutorial&lt;/a&gt;, you can leave the project as it is, but if you want to ensure everything is clean or start from the same point, you can run the following commands:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;## Repository link https://github.com/kevinccbsg/react-router-tutorial-devto
git reset --hard
git clean -d -f
git checkout 01-outlet-nested-routes-links
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;initial-route-structure&quot;&gt;Initial route structure&lt;/h3&gt;
&lt;p&gt;Let's first prepare our routes file. We'll modify the &lt;code&gt;src/AppRoutes.tsx&lt;/code&gt; file to use the &lt;code&gt;Component&lt;/code&gt; property instead of &lt;code&gt;element&lt;/code&gt; for our main pages. We use &lt;code&gt;Component&lt;/code&gt; instead of &lt;code&gt;element&lt;/code&gt; when we want to directly pass a reference to the component without needing JSX ().&lt;/p&gt;
&lt;p&gt;The code will look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// filepath: /root/brikev/react-router-tutorial-devto/src/AppRoutes.tsx
import { createBrowserRouter } from &quot;react-router&quot;;
import ContactsPage from &quot;./pages/Contacts&quot;;
import ContactForm from &quot;./pages/ContactForm&quot;;

const AppRoutes = createBrowserRouter([
  {
    path: &quot;/&quot;,
    Component: ContactsPage,
  },
  {
    path: &quot;/contacts/new&quot;,
    Component: ContactForm,
  },
  {
    path: &quot;/about&quot;,
    element: &amp;lt;div&amp;gt;About&amp;lt;/div&amp;gt;,
  },
  {
    path: &quot;*&quot;,
    element: &amp;lt;div&amp;gt;Not Found&amp;lt;/div&amp;gt;,
  },
]);

export default AppRoutes;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, we simply indicate which component should be displayed for each route.&lt;/p&gt;
&lt;h3 id=&quot;create-our-pages&quot;&gt;Create our pages&lt;/h3&gt;
&lt;p&gt;Now let's create the &lt;code&gt;ContactsPage&lt;/code&gt; and &lt;code&gt;ContactForm&lt;/code&gt; components. To organize ourselves better, we'll save them in a new folder &lt;code&gt;src/pages&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/pages/Contacts.tsx&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// filepath: /root/brikev/react-router-tutorial-devto/src/pages/Contacts.tsx
import { ScrollArea } from &quot;@/components/ui/scroll-area&quot;;
import { Button } from &quot;@/components/ui/button&quot;;

const contacts = [ // mock data
  {
    &quot;id&quot;: &quot;1&quot;,
    &quot;firstName&quot;: &quot;Jane&quot;,
    &quot;lastName&quot;: &quot;Doe&quot;,
    &quot;username&quot;: &quot;jane_doe&quot;,
    &quot;avatar&quot;: &quot;https://i.pravatar.cc/150?img=1&quot;,
    &quot;email&quot;: &quot;*Emails are not allowed*&quot;,
    &quot;phone&quot;: &quot;+1 555-1234&quot;,
    &quot;favorite&quot;: true
  },
  {
    &quot;id&quot;: &quot;2&quot;,
    &quot;firstName&quot;: &quot;John&quot;,
    &quot;lastName&quot;: &quot;Smith&quot;,
    &quot;username&quot;: &quot;john_smith&quot;,
    &quot;avatar&quot;: &quot;https://i.pravatar.cc/150?img=12&quot;,
    &quot;email&quot;: &quot;*Emails are not allowed*&quot;,
    &quot;phone&quot;: &quot;+1 555-5678&quot;,
    &quot;favorite&quot;: true
  }
];

const ContactsPage = () =&amp;gt; {
  return (
    &amp;lt;div className=&quot;h-screen grid grid-cols-[300px_1fr]&quot;&amp;gt;
      {/* Sidebar */}
      &amp;lt;div className=&quot;border-r p-4 flex flex-col gap-4&quot;&amp;gt;
        &amp;lt;Button className=&quot;w-full&quot; variant=&quot;secondary&quot; asChild&amp;gt;
          &amp;lt;a href=&quot;/contacts/new&quot;&amp;gt;
            New
          &amp;lt;/a&amp;gt;
        &amp;lt;/Button&amp;gt;
        &amp;lt;ScrollArea className=&quot;flex-1&quot;&amp;gt;
        &amp;lt;div className=&quot;flex flex-col gap-1 mt-4&quot;&amp;gt;
          {contacts.map(contact =&amp;gt; (
            &amp;lt;Button
              key={contact.id}
              className=&quot;justify-start&quot;
              asChild
            &amp;gt;
              &amp;lt;a href={`/contacts/${contact.id}`}&amp;gt;
                {contact.firstName} {contact.lastName}
              &amp;lt;/a&amp;gt;
            &amp;lt;/Button&amp;gt;
          ))}
        &amp;lt;/div&amp;gt;
      &amp;lt;/ScrollArea&amp;gt;
      &amp;lt;/div&amp;gt;
      {/* Detail View */}
      &amp;lt;div className=&quot;p-8&quot;&amp;gt;
        Contact page
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default ContactsPage;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;src/pages/ContactForm.tsx&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// filepath: /root/brikev/react-router-tutorial-devto/src/pages/ContactForm.tsx
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';

const ContactForm = () =&amp;gt; {
  return (
    &amp;lt;div className=&quot;max-w-md mx-auto&quot;&amp;gt;
      &amp;lt;h1 className=&quot;text-2xl font-bold mb-4&quot;&amp;gt;Create New Contact&amp;lt;/h1&amp;gt;
      &amp;lt;form className=&quot;space-y-4&quot;&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;Label className=&quot;mb-2&quot; htmlFor=&quot;firstName&quot;&amp;gt;First Name&amp;lt;/Label&amp;gt;
          &amp;lt;Input type=&quot;text&quot; id=&quot;firstName&quot; name=&quot;firstName&quot; required /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;Label className=&quot;mb-2&quot; htmlFor=&quot;lastName&quot;&amp;gt;Last Name&amp;lt;/Label&amp;gt;
          &amp;lt;Input type=&quot;text&quot; id=&quot;lastName&quot; name=&quot;lastName&quot; required /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;Label className=&quot;mb-2&quot; htmlFor=&quot;username&quot;&amp;gt;Username&amp;lt;/Label&amp;gt;
          &amp;lt;Input type=&quot;text&quot; id=&quot;username&quot; name=&quot;username&quot; required /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;Label className=&quot;mb-2&quot; htmlFor=&quot;email&quot;&amp;gt;Email&amp;lt;/Label&amp;gt;
          &amp;lt;Input type=&quot;email&quot; id=&quot;email&quot; name=&quot;email&quot; required /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;Label className=&quot;mb-2&quot; htmlFor=&quot;phone&quot;&amp;gt;Phone&amp;lt;/Label&amp;gt;
          &amp;lt;Input type=&quot;tel&quot; id=&quot;phone&quot; name=&quot;phone&quot; required /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;Label className=&quot;mb-2&quot; htmlFor=&quot;avatar&quot;&amp;gt;Avatar (Optional)&amp;lt;/Label&amp;gt;
          &amp;lt;Input type=&quot;url&quot; id=&quot;avatar&quot; name=&quot;avatar&quot; /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;Button type=&quot;submit&quot;&amp;gt;
          Create Contact
        &amp;lt;/Button&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default ContactForm;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you visit &lt;code&gt;/&lt;/code&gt; or &lt;code&gt;/contacts/new&lt;/code&gt;, you should see both pages, but... something is not right:&lt;/p&gt;
&lt;p&gt;The form is displayed as a standalone page, without maintaining the sidebar layout. Also, when using the &quot;New&quot; link, we see a full page reload, indicating that we don't yet have a properly configured SPA navigation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l7xdpuokho451ba9d79v.png&quot; alt=&quot;Homepage without outlet&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/clcuaphq917ub6ulvwnp.png&quot; alt=&quot;Form page without sidebar&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;nest-routes-within-a-layout&quot;&gt;Nest routes within a layout&lt;/h3&gt;
&lt;p&gt;Now that we have both pages, let's make the &lt;code&gt;/contacts/new&lt;/code&gt; route not standalone but rendered within the main contacts page layout (i.e., within &lt;code&gt;ContactsPage&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// filepath: /root/brikev/react-router-tutorial-devto/src/AppRoutes.tsx
import { createBrowserRouter } from &quot;react-router&quot;;
import ContactsPage from &quot;./pages/Contacts&quot;;
import ContactForm from &quot;./pages/ContactForm&quot;;

const AppRoutes = createBrowserRouter([
  {
    path: &quot;/&quot;,
    Component: ContactsPage,
    children: [
      {
        path: &quot;contacts/new&quot;,
        Component: ContactForm,
      },
    ],
  },
  {
    path: &quot;/about&quot;,
    element: &amp;lt;div&amp;gt;About&amp;lt;/div&amp;gt;,
  },
  {
    path: &quot;*&quot;,
    element: &amp;lt;div&amp;gt;Not Found&amp;lt;/div&amp;gt;,
  },
]);

export default AppRoutes;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We now have the nested route, but it won't display until we specify where the &lt;code&gt;children&lt;/code&gt; should appear. For that, we use the &lt;code&gt;Outlet&lt;/code&gt; component.&lt;/p&gt;
&lt;h3 id=&quot;display-child-routes-with-outlet&quot;&gt;Display child routes with Outlet&lt;/h3&gt;
&lt;p&gt;Within the &lt;code&gt;ContactsPage&lt;/code&gt; component, let's import and place &lt;code&gt;Outlet&lt;/code&gt; exactly where we want the child pages to be displayed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// filepath: /root/brikev/react-router-tutorial-devto/src/pages/Contacts.tsx
// Import the component
import { Link, Outlet } from &quot;react-router&quot;;
// ...existing code...
{/* Detail View */}
&amp;lt;div className=&quot;p-8&quot;&amp;gt;
  &amp;lt;Outlet /&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;spa-navigation-with-link&quot;&gt;SPA navigation with Link&lt;/h3&gt;
&lt;p&gt;So far, we've used &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; for links, but that causes a full page reload. To enable client-side navigation (without reloading), we need to use the &lt;code&gt;Link&lt;/code&gt; component from &lt;code&gt;react-router&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;a href=&quot;/contacts/new&quot;&amp;gt;New&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;Link to=&quot;/contacts/new&quot; viewTransition&amp;gt;
  New
&amp;lt;/Link&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;Link to={`/contacts/${contact.id}`} viewTransition&amp;gt;
  {contact.firstName} {contact.lastName}
&amp;lt;/Link&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;viewTransition&lt;/code&gt; prop enables automatic route transition animations (if the browser supports it). Very useful for making navigation feel smoother! By default, it does a fade-in, but this can be customized much further. We can discuss this in a future post.&lt;/p&gt;
&lt;h3 id=&quot;recap&quot;&gt;Recap&lt;/h3&gt;
&lt;p&gt;So far, we've achieved:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A main route with a layout&lt;/li&gt;
&lt;li&gt;Nested routes displayed within that layout&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;Link&lt;/code&gt; instead of &lt;code&gt;a&lt;/code&gt; to avoid reloads&lt;/li&gt;
&lt;li&gt;Enabling visual transitions between routes&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;what-s-next-&quot;&gt;What's next?&lt;/h3&gt;
&lt;p&gt;In the next part, we will do something very powerful: render dynamic data based on the URL and learn how to use loaders to fetch data.&lt;br&gt;
Let's dive into the magic of React Router v7!&lt;/p&gt;
&lt;p&gt;See you in part 3.&lt;/p&gt;
</description>
<category>React Router</category>
<guid isPermaLink="true">https://coderlegion.com/3682/react-router-data-mode-part-2-nested-routes-and-outlets</guid>
<pubDate>Fri, 04 Jul 2025 09:09:28 +0000</pubDate>
</item>
<item>
<title>React Router Data Mode: Part 1 - Installation and first routes</title>
<link>https://coderlegion.com/3526/react-router-data-mode-part-1-installation-and-first-routes</link>
<description>&lt;p&gt;In this series, we will focus on the &lt;strong&gt;Data Mode&lt;/strong&gt;, which is undoubtedly my favorite for creating well-structured and maintainable SPAs.&lt;/p&gt;
&lt;p&gt;This series will have several parts, which you can see below:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Installation and first routes&lt;/li&gt;
&lt;li&gt;Nested routes and Outlet&lt;/li&gt;
&lt;li&gt;Loaders&lt;/li&gt;
&lt;li&gt;Routes with parameters, useRouteLoaderData, and useParams&lt;/li&gt;
&lt;li&gt;useParams, Navlink&lt;/li&gt;
&lt;li&gt;Actions&lt;/li&gt;
&lt;li&gt;Multiple actions and form handling on a single page&lt;/li&gt;
&lt;li&gt;Form validation and useFetcher&lt;/li&gt;
&lt;li&gt;Optimistic UI with useFetcher&lt;/li&gt;
&lt;li&gt;Testing with Vitest and React Testing Library&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All parts will be explained in this repository, which already comes prepared with some components and style libraries like &lt;strong&gt;shadcn/ui&lt;/strong&gt; and &lt;strong&gt;Tailwind&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-are-we-going-to-build-&quot;&gt;What are we going to build?&lt;/h3&gt;
&lt;p&gt;A contact application where we will practice nested routes, data loading and mutation, navigation, validations, etc.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n6xpdv53npyen4bqoj8w.gif&quot; alt=&quot;Contact web app demo for React Router&quot;&gt;&lt;/p&gt;
&lt;p&gt;Let's get started!&lt;/p&gt;
&lt;h3 id=&quot;installation-and-first-routes&quot;&gt;Installation and first routes&lt;/h3&gt;
&lt;p&gt;The first step is to clone the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kevinccbsg/react-router-tutorial-devto&quot;&gt;base repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In your terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone *Emails are not allowed*:kevinccbsg/react-router-tutorial-devto.git
# Move to the initial tag
git checkout 00-init-project
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm i
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This project is created with &lt;strong&gt;Vite&lt;/strong&gt; and has the &lt;strong&gt;shadcn/ui&lt;/strong&gt; library integrated. We won't explain that part here, but if you'd like a tutorial on Vite + shadcn, let me know in the comments.&lt;/p&gt;
&lt;p&gt;Now start the project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will see something very simple on the screen: an &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; with the text &quot;Welcome to React!&quot;. Let's change that and start using &lt;strong&gt;React Router (Data Mode)&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;install-react-router&quot;&gt;Install React Router&lt;/h3&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm i react-router
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a new file called &lt;code&gt;src/AppRoutes.tsx&lt;/code&gt;, which will contain our route configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { createBrowserRouter } from &quot;react-router&quot;;

const AppRoutes = createBrowserRouter([
  {
    path: &quot;/&quot;,
    element: &amp;lt;div&amp;gt;Home&amp;lt;/div&amp;gt;,
  },
  {
    path: &quot;/about&quot;,
    element: &amp;lt;div&amp;gt;About&amp;lt;/div&amp;gt;,
  },
  {
    path: &quot;*&quot;,
    element: &amp;lt;div&amp;gt;Not Found&amp;lt;/div&amp;gt;,
  },
]);

export default AppRoutes;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unlike the Declarative Mode (where we use &lt;code&gt;&amp;lt;Routes&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;Route&amp;gt;&lt;/code&gt;), in Data Mode, we define routes as objects. Each object represents a route and can include elements like &lt;code&gt;element&lt;/code&gt;, &lt;code&gt;loader&lt;/code&gt;, &lt;code&gt;action&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;In this example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/&lt;/code&gt; displays a simple &quot;Home&quot;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/about&lt;/code&gt; displays &quot;About&quot;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*&lt;/code&gt; captures any undefined route (what React Router calls a &quot;splat&quot;) and displays &quot;Not Found&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;connect-the-router-with-react&quot;&gt;Connect the router with React&lt;/h3&gt;
&lt;p&gt;To activate React Router, we need to connect it in &lt;code&gt;main.tsx&lt;/code&gt;. Edit the file as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import { RouterProvider } from &quot;react-router&quot;;
import router from './AppRoutes';

createRoot(document.getElementById('root')!).render(
  &amp;lt;StrictMode&amp;gt;
    &amp;lt;RouterProvider router={router} /&amp;gt;
  &amp;lt;/StrictMode&amp;gt;,
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Done! If you visit &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;/about&lt;/code&gt;, or any other route, you should see the corresponding content.&lt;/p&gt;
&lt;h3 id=&quot;what-s-next--1&quot;&gt;What's next?&lt;/h3&gt;
&lt;p&gt;In the next part, we will build the real structure of the application, see how to use &lt;code&gt;Outlet&lt;/code&gt; for nested routes, &lt;code&gt;Link&lt;/code&gt;, and create a base layout.&lt;/p&gt;
&lt;p&gt;See you in part 2.&lt;/p&gt;
</description>
<category>React Router</category>
<guid isPermaLink="true">https://coderlegion.com/3526/react-router-data-mode-part-1-installation-and-first-routes</guid>
<pubDate>Fri, 27 Jun 2025 07:42:23 +0000</pubDate>
</item>
<item>
<title>React Router v7: The Evolution React Needed</title>
<link>https://coderlegion.com/3517/react-router-v7-the-evolution-react-needed</link>
<description>&lt;p&gt;If you're reading this, you're probably working with React or learning how to use it. And if that’s the case, you’ve surely heard of &lt;code&gt;react-router&lt;/code&gt;, the most popular library for routing in React apps for years.&lt;/p&gt;
&lt;p&gt;But what you may not know is that in its latest version, React Router has evolved significantly. It now incorporates concepts from &lt;strong&gt;Remix&lt;/strong&gt; like &lt;code&gt;loaders&lt;/code&gt;, &lt;code&gt;actions&lt;/code&gt;, &lt;code&gt;ErrorBoundaries&lt;/code&gt;, &lt;code&gt;SSR support&lt;/code&gt;, and more.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Remix&lt;/strong&gt;? That’s right. The Remix framework has always been built on top of React Router. But with this new version (v7), the experience has been unified—now you can use that same architecture directly in React Router, without needing to adopt a full framework.&lt;/p&gt;
&lt;h3 id=&quot;what-does-react-router-v7-offer-&quot;&gt;What Does React Router v7 Offer?&lt;/h3&gt;
&lt;p&gt;The best way to understand what React Router v7 brings is by looking at its &lt;strong&gt;three usage modes&lt;/strong&gt;, each designed for different levels of complexity and application needs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Declarative Mode&lt;/li&gt;
&lt;li&gt;Data Mode&lt;/li&gt;
&lt;li&gt;Framework Mode&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let’s break down what each one is and when to use them.&lt;/p&gt;
&lt;h4 id=&quot;declarative-mode&quot;&gt;Declarative Mode&lt;/h4&gt;
&lt;p&gt;This is the classic approach you’re probably already familiar with. You use components like &lt;code&gt;&amp;lt;Routes&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;Route&amp;gt;&lt;/code&gt; to define your routes, along with hooks like &lt;code&gt;useNavigate&lt;/code&gt;, &lt;code&gt;useParams&lt;/code&gt;, and so on.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&amp;lt;Routes&amp;gt;
  &amp;lt;Route index element={&amp;lt;Home /&amp;gt;} /&amp;gt;
  &amp;lt;Route path=&quot;about&quot; element={&amp;lt;About /&amp;gt;} /&amp;gt;

  &amp;lt;Route element={&amp;lt;AuthLayout /&amp;gt;}&amp;gt;
    &amp;lt;Route path=&quot;login&quot; element={&amp;lt;Login /&amp;gt;} /&amp;gt;
    &amp;lt;Route path=&quot;register&quot; element={&amp;lt;Register /&amp;gt;} /&amp;gt;
  &amp;lt;/Route&amp;gt;

  &amp;lt;Route path=&quot;concerts&quot;&amp;gt;
    &amp;lt;Route index element={&amp;lt;ConcertsHome /&amp;gt;} /&amp;gt;
    &amp;lt;Route path=&quot;:city&quot; element={&amp;lt;City /&amp;gt;} /&amp;gt;
    &amp;lt;Route path=&quot;trending&quot; element={&amp;lt;Trending /&amp;gt;} /&amp;gt;
  &amp;lt;/Route&amp;gt;
&amp;lt;/Routes&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This mode is perfect if you want a simple SPA using your own bundler (Vite, Webpack...), managing state with Zustand, Redux, or react-query, and you don’t need server-side rendering (SSR).&lt;/p&gt;
&lt;h4 id=&quot;data-mode&quot;&gt;Data Mode&lt;/h4&gt;
&lt;p&gt;This is where React Router v7 really transforms. Instead of using components to define routes, you use a route config object. Each route can have a loader (to fetch data) and an action (to handle forms or mutations).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const router = createBrowserRouter([
  {
    path: &quot;/&quot;,
    element: &amp;lt;div&amp;gt;Hello World&amp;lt;/div&amp;gt;,
  },
]);

ReactDOM.createRoot(document.getElementById(&quot;root&quot;)).render(
  &amp;lt;RouterProvider router={router} /&amp;gt;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A more complete example with a &lt;code&gt;loader&lt;/code&gt; and &lt;code&gt;action&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;createBrowserRouter([
  {
    path: &quot;/items&quot;,
    loader,
    action,
    Component: Items,
  },
]);

async function loader() {
  const items = await fakeDb.getItems();
  return { items };
}

async function action({ request }) {
  const data = await request.formData();
  await fakeDb.addItem({ title: data.get(&quot;title&quot;) });
  return { ok: true };
}

function Items() {
  const data = useLoaderData();
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;TodoList items={data.items} /&amp;gt;
      &amp;lt;Form method=&quot;post&quot;&amp;gt;
        &amp;lt;input type=&quot;text&quot; name=&quot;title&quot; /&amp;gt;
        &amp;lt;button type=&quot;submit&quot;&amp;gt;Crear&amp;lt;/button&amp;gt;
      &amp;lt;/Form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This mode is ideal if you're building a complex SPA and want to avoid useEffect chaos, centralizing your data flow. You can also keep your current stack and deploy wherever you want (Vercel, Netlify, Firebase, etc.).&lt;/p&gt;
&lt;p&gt; I recommend checking out this &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kevinccbsg/react-router-tutorials/tree/main/react-router-data&quot;&gt;ejemplos&lt;/a&gt;, and stay tuned—I'll be publishing more articles on how to make the most of this mode.&lt;/p&gt;
&lt;h4 id=&quot;framework-mode&quot;&gt;Framework Mode&lt;/h4&gt;
&lt;p&gt;This mode turns React Router into a full framework with SSR support. You start from scratch using a CLI, and it’s designed for apps that need SEO or super-fast initial loads.&lt;/p&gt;
&lt;p&gt;It has the best TypeScript support, shares the &lt;code&gt;loaders&lt;/code&gt; and &lt;code&gt;actions&lt;/code&gt; concept from Data Mode, and gives you full control.&lt;/p&gt;
&lt;p&gt; However, keep in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need to define your deployment setup from the start.&lt;/li&gt;
&lt;li&gt;Not all libraries work well with SSR.&lt;/li&gt;
&lt;li&gt;Your team should understand the difference between CSR and SSR.&lt;/li&gt;
&lt;li&gt;It works best with Tailwind or styling approaches compatible with SSR.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That said, if your app is private or doesn’t need SEO, I’d recommend sticking with Declarative or Data Mode to avoid unnecessary complexity.&lt;/p&gt;
&lt;h3 id=&quot;so-is-it-worth-it-&quot;&gt;So, Is It Worth It?&lt;/h3&gt;
&lt;p&gt;Many React frameworks are adding complexity to the frontend with their latest updates. React Router v7 gives you a clear, flexible alternative:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start with Declarative Mode if you just need routing.&lt;/li&gt;
&lt;li&gt;Move to Data Mode to better manage data and forms.&lt;/li&gt;
&lt;li&gt;And go full Framework Mode only if SSR is truly required.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All without locking yourself into a single approach from day one.&lt;/p&gt;
&lt;p&gt; In upcoming posts, I’ll dive deeper into &lt;code&gt;loaders&lt;/code&gt;, &lt;code&gt;actions&lt;/code&gt;, and data-mode architecture—building projects from scratch, testing, and real-world integrations.&lt;/p&gt;
&lt;p&gt;Are you interested in React Router v7? Want to see practical examples or comparisons with other solutions? Let me know in the comments—I’d love to hear from you.&lt;/p&gt;
</description>
<category>Frontend Dev</category>
<guid isPermaLink="true">https://coderlegion.com/3517/react-router-v7-the-evolution-react-needed</guid>
<pubDate>Thu, 26 Jun 2025 22:08:37 +0000</pubDate>
</item>
<item>
<title>Post about Use and Misuse of AI in Localized Content and Dynamic Creative Work</title>
<link>https://coderlegion.com/1084/post-about-use-and-misuse-of-ai-in-localized-content-and-dynamic-creative-work</link>
<description>&lt;p&gt;Artificial Intelligence (AI) has emerged as a transformative tool across industries. From automating repetitive tasks to generating creative content, it has become an ally to many. However, like any tool, its value lies in how we use it. When misapplied—particularly in areas like localized content creation—it can lead to inaccuracies, cultural insensitivity, and poorly tailored results.&lt;br&gt;
Also in programming, the use of open-source libraries, frameworks and rapidly changing code conventions that are constantly in development. Framework dependencies as upgrades happen soon turn out to cause issues in program development. &lt;/p&gt;
&lt;p&gt;Awhile ago when i was trying to unbundle webpack as a newbie in React.js. i found a video that took me on a course on React and React-router using Webpack.&lt;br&gt;
Before i realized that the video was outdated, i ran into error from getting up the webpack structure. &lt;br&gt;
I noticed that their code was working but each time i try it out the, code does not give me the expected result. &lt;br&gt;
I took the error message to Gemini and the response was based on the outdated library i was using. &lt;br&gt;
It was when I rephrased and asked &lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Is the current webpack library i was using outdated and is there a&lt;br&gt;
new convention in implementing this code to resolve these errors, also&lt;br&gt;
sending my package.json.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This new query was the beginning of the resolution of the error-bug.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Old Webpack Configuration (Pre-Version 4)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;// webpack.config.js (Outdated)
const path = require('path');

module.exports = {
  entry: './src/index.js', // Entry point
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js', // Output bundle
  },
  module: {
    rules: [
      {
        test: /\.js$/, // Handling JavaScript files
        exclude: /node_modules/,
        loader: 'babel-loader', // Transpile ES6+ to ES5
      },
    ],
  },
  devServer: {
    contentBase: path.join(__dirname, 'dist'), // Serve static files
    port: 8080,
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;Modern Webpack Configuration (Version 4 and Beyond)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;// webpack.config.js (Modern)
const path = require('path');

module.exports = {
  mode: 'development', // Mode: &quot;development&quot; or &quot;production&quot;
  entry: './src/index.js', // Entry point
  output: {
    filename: 'main.js', // Default: &quot;main.js&quot; if not specified
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.js$/, // Handle JavaScript files
        exclude: /node_modules/,
        use: 'babel-loader', // Simplified syntax for loaders
      },
    ],
  },
  devServer: {
    static: './dist', // Serve static files (simplified from contentBase)
    port: 3000, // Optional: Default port is 8080
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Modern tools like create-react-app and Vite have revolutionized how React applications are built by abstracting away complex Webpack configurations. With just a single command, these tools scaffold a fully functional React app with sensible defaults, making them ideal for beginners or developers who want to focus more on coding than configuration. &lt;br&gt;
For example, create-react-app and its TypeScript counterpart (&lt;code&gt;create-react-app-template-typescript&lt;/code&gt;) provide a zero-config setup with Webpack under the hood. &lt;br&gt;
On the other hand, Vite, a newer alternative, offers a lightweight and faster development experience by leveraging native ES modules and modern build tools like Rollup.&lt;/p&gt;
&lt;p&gt;However, for developers who want to understand the inner workings of their build tools or customize their setup, starting from scratch with &lt;code&gt;npm init -y&lt;/code&gt; and manually configuring Webpack is still a valuable exercise. Below, I’ll show how these tools compare to a manual Webpack setup.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Using create-react-app With create-react-app, setting up a React app&lt;br&gt;
is as simple as running:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;npx create-react-app my-app
cd my-app
npm start
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;To add TypeScript support, use:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;npx create-react-app my-app --template typescript
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This generates a React app with Webpack preconfigured and ready to go. You don’t need to touch webpack.config.js unless you eject the app (which is rarely necessary). The simplicity makes it perfect for most use cases.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Using Vite to Create a React App Vite provides an even faster and more&lt;br&gt;
modern approach:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;npm create vite@latest my-app
cd my-app
npm install
npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also specify React and TypeScript templates during setup:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm create vite@latest my-app -- --template react
npm create vite@latest my-app -- --template react-ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vite eliminates the need for Webpack entirely, offering faster builds and hot module replacement (HMR).&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Manual Webpack Setup For a manual setup, follow these steps:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;1-initialize-a-new-project-&quot;&gt;1. Initialize a new project:&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt; mkdir my-app
    cd my-app
    npm init -y
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-install-webpack-and-react-dependencies-&quot;&gt;2. Install Webpack and React dependencies:&lt;/h3&gt;
&lt;p&gt;npm install react react-dom webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env @babel/preset-react html-webpack-plugin --save-dev&lt;/p&gt;
&lt;h3 id=&quot;3-create-a-webpack-config-js-file-to-configure-webpack-&quot;&gt;3. Create a webpack.config.js file to configure Webpack.&lt;/h3&gt;
&lt;p&gt;Below is a sample Webpack configuration for a React app:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; // webpack.config.js

    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
      mode: 'development', // Switch to &quot;production&quot; for production builds
      entry: './src/index.js', // Main entry point
      output: {
        path: path.resolve(__dirname, 'dist'), // Output directory
        filename: 'bundle.js', // Output bundle name
        clean: true, // Cleans 'dist' folder before each build
      },
      module: {
        rules: [
          {
            test: /\.js$/, // Handle JavaScript files
            exclude: /node_modules/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: ['@babel/preset-env', '@babel/preset-react'], // Transpile modern JavaScript and React JSX
              },
            },
          },
          {
            test: /\.css$/, // Handle CSS files
            use: ['style-loader', 'css-loader'], // Inject styles into DOM
          },
        ],
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: './public/index.html', // Template HTML file
        }),
      ],
      devServer: {
        static: path.resolve(__dirname, 'dist'), // Serve static files from 'dist'
        port: 3000, // Development server port
        hot: true, // Enable hot module replacement
      },
      resolve: {
        extensions: ['.js', '.jsx'], // Resolve these extensions in imports
      },
    };
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-create-the-project-structure-&quot;&gt;4. Create the project structure:&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  my-app/

      ├── src/

          │├── index.js

          │├── App.js

      ├── public/

         │ ├── index.html

      ├── webpack.config.js

     ├── package.json
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;5-add-the-following-scripts-to-your-package-json-&quot;&gt;5. Add the following scripts to your package.json:&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;json

    &quot;scripts&quot;: {
      &quot;start&quot;: &quot;webpack serve --open&quot;,
      &quot;build&quot;: &quot;webpack&quot;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;6-run-the-app-&quot;&gt;6. Run the app:&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;   npm start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Issues that arise in open-source library dependent framework&lt;br&gt;
React as a Project has been on version 18 which there have been crash courses out there and documentations. Ai which uses the Large Language Model (LLM) from either google or any backbone server and data warehouse feed on the outdated information which is used to train this AI.&lt;br&gt;
Most of the documentations are not being updated which lead to outdated response when the current libraries of React have depreciated because react 19 is out.&lt;br&gt;
An example of this is the new way &quot;use&quot; is used to fetch data from an api instate of the joint work of the useEffect and storing state with useStates.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Outdated Approach: Data Fetching Using useEffect and useState&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;function OldUserProfile() {
  const [userData, setUserData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() =&amp;gt; {
    fetch('https://api.example.com/user')
      .then(res =&amp;gt; res.json())
      .then(data =&amp;gt; {
        setUserData(data);
        setLoading(false);
      });
  }, []);

  if (loading) return &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;;
  
  return (
    &amp;lt;div className=&quot;p-4&quot;&amp;gt;
      &amp;lt;h1 className=&quot;text-xl font-bold&quot;&amp;gt;User Profile&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;Name: {userData.name}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;Email: {userData.email}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;Modern Approach: Data Fetching Using the use() Hook&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;   // Modern approach - app/root.jsx or similar
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { use } from 'react';

// Example of modern data fetching with 'use'
function UserProfile() {
  // The new 'use' approach for data fetching
  const userData = use(fetch('https://api.example.com/user')
    .then(res =&amp;gt; res.json()));

  return (
    &amp;lt;div className=&quot;p-4&quot;&amp;gt;
      &amp;lt;h1 className=&quot;text-xl font-bold&quot;&amp;gt;User Profile&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;Name: {userData.name}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;Email: {userData.email}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Key Improvements:&lt;/p&gt;
&lt;p&gt;The use() hook eliminates the need for useEffect and useState when fetching data.&lt;br&gt;
Data fetching is more declarative and less verbose.&lt;br&gt;
This works seamlessly with React's server components, aligning well with modern rendering patterns.&lt;/p&gt;
&lt;p&gt;Also React-router initially wrap the provider tag in the index.js file, then wrap the file in the app.js using the browser router as router, routes and nav links and the new use of links instead&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Outdated React Router Conventions&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

ReactDOM.render(
  &amp;lt;BrowserRouter&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/BrowserRouter&amp;gt;,
  document.getElementById('root')
);

// App.js
import React from 'react';
import { Route, Switch, NavLink } from 'react-router-dom';

function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;nav&amp;gt;
        &amp;lt;NavLink to=&quot;/&quot;&amp;gt;Home&amp;lt;/NavLink&amp;gt;
        &amp;lt;NavLink to=&quot;/about&quot;&amp;gt;About&amp;lt;/NavLink&amp;gt;
      &amp;lt;/nav&amp;gt;
      &amp;lt;Switch&amp;gt;
        &amp;lt;Route path=&quot;/&quot; exact&amp;gt;
          &amp;lt;h1&amp;gt;Home Page&amp;lt;/h1&amp;gt;
        &amp;lt;/Route&amp;gt;
        &amp;lt;Route path=&quot;/about&quot;&amp;gt;
          &amp;lt;h1&amp;gt;About Page&amp;lt;/h1&amp;gt;
        &amp;lt;/Route&amp;gt;
      &amp;lt;/Switch&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;Modern React Router Conventions&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;const router = createBrowserRouter([
  {
    path: &quot;/&quot;,
    element: &amp;lt;Root /&amp;gt;,
    children: [
      {
        path: &quot;user&quot;,
        element: &amp;lt;UserProfile /&amp;gt;,
        // You can also use loaders for data fetching
        loader: async () =&amp;gt; {
          return fetch('https://api.example.com/user');
        }
      },
      {
        path: &quot;review-booking&quot;,
        element: &amp;lt;ReviewBooking /&amp;gt;,
      },
      // ... other routes
    ],
  },
]);

// Root component
function Root() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;nav&amp;gt;
        {/* Modern Link usage */}
        &amp;lt;Link to=&quot;/user&quot;&amp;gt;User Profile&amp;lt;/Link&amp;gt;
        &amp;lt;Link to=&quot;/review-booking&quot;&amp;gt;Review Booking&amp;lt;/Link&amp;gt;
      &amp;lt;/nav&amp;gt;
      &amp;lt;Outlet /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

// App component
export default function App() {
  return &amp;lt;RouterProvider router={router} /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;The Right Way to Use AI&lt;/p&gt;
&lt;p&gt;Localized Content Creation&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;AI has immense potential to assist in creating localized content. It can help translate, adapt styles, and even suggest culturally relevant expressions. However, the key here is collaboration, not replacement. AI can provide a starting point, but the human touch ensures that the content resonates with the intended audience. Nuances in language, cultural references, and local idioms are often things AI struggles to fully grasp.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Code Writing Assistance&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;AI has become a programmer’s best friend, especially when it comes to generating boilerplate code or debugging complex problems. Tools like GitHub Copilot and ChatGPT can suggest solutions, optimize algorithms, and even generate entire functions. But relying on AI blindly can result in brittle, unoptimized, or even insecure code. Always review and test the generated code thoroughly. AI should complement your skills, not replace them.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Feedback in Article Journaling&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When writing articles or journaling, AI can act as a brainstorming partner. Whether you’re stuck on an introduction or need help refining a paragraph, AI tools can offer suggestions. However, their outputs should be critically assessed. AI doesn’t understand your voice or your audience’s expectations—it generates content based on patterns, not emotions or intent.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;The Wrong Way to Use AI&lt;/p&gt;
&lt;p&gt;Misusing AI in Localized Content&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the most common pitfalls is treating AI as a &quot;one-size-fits-all&quot; solution. For example, using AI to directly translate content for different regions without a human review can result in awkward phrasing or offensive translations. Localization isn’t just about language—it’s about context, tone, and cultural relevance. Ignoring this can alienate your audience rather than engaging them.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Blindly Trusting AI-Generated Code&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;AI-generated code can often appear perfect at first glance, but it may contain subtle bugs, inefficiencies, or security vulnerabilities. Copy-pasting code without understanding it risks introducing issues into your project. Always review the code, test extensively, and adapt it to fit your specific requirements.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Overreliance on AI for Creativity&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While AI can assist with creativity, it’s not a replacement for human originality. Overusing AI for journaling or content creation can lead to generic, uninspired results. The magic of writing comes from your personal insights, emotions, and unique perspective—something AI cannot replicate.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/1084/post-about-use-and-misuse-of-ai-in-localized-content-and-dynamic-creative-work</guid>
<pubDate>Wed, 05 Feb 2025 12:32:25 +0000</pubDate>
</item>
<item>
<title>Learn how to Implement Public, Private, and Protected Routes in your React Application</title>
<link>https://coderlegion.com/396/learn-how-to-implement-public-private-and-protected-routes-in-your-react-application</link>
<description>&lt;h3 id=&quot;react-public-protected-and-private-routes-a-comprehensive-guide&quot;&gt;React Public, Protected, and Private Routes: A Comprehensive Guide&lt;/h3&gt;
&lt;p&gt;In certain applications, you might need to implement &lt;strong&gt;role-based authorization&lt;/strong&gt; for your users to access certain pages and be restricted to others. For instance, in an e-commerce application, your users could sign up as a seller or a buyer, and based on their role, you give them access to certain pages in the application. Each page not accessible to a user due to their role is hidden in a &lt;strong&gt;private route&lt;/strong&gt;, and every other page accessible to the users irrespective of their roles is a &lt;strong&gt;public route&lt;/strong&gt;. Aside from private and public routes, there are also protected routes. To access the pages hidden in a protected route, your users must be &lt;strong&gt;authenticated&lt;/strong&gt;—signed up or logged in. &lt;br&gt;
In a multiple-role-based application, you need to implement these routes to have certain accesses and restrictions in place for your users. Without them, your application will be disorganized and confusing for your users and you, especially when accessing users' data.&lt;br&gt;
React Router, the routing library built for React applications, does not have these pre-installed features, so you need to write special configurations for them in your application. In this article, you’ll learn how to create these three special routes for your applications and implement &lt;strong&gt;role-based authorization&lt;/strong&gt; in your React apps through them.&lt;/p&gt;
&lt;h4 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h4&gt;
&lt;p&gt;Although this is a straightforward article, you need to have a good understanding of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;React Router&lt;/li&gt;
&lt;li&gt;Authorization and Authentication. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nevertheless, I’ll briefly introduce the mentioned libraries.&lt;/p&gt;
&lt;h4 id=&quot;brief-introduction-to-react-react-router-authorization-and-authentication&quot;&gt;Brief Introduction to React, React Router, Authorization, and Authentication&lt;/h4&gt;
&lt;p&gt;React is a popular JavaScript frontend library built by Facebook to help developers easily start up a web application. It’s a Single Page Application (SPA) library which renders your app in a single HTML file rather than having multiple HTML files for all your application’s pages. React allows you to break down frequently occurring aspects of your app into reusable components, preventing unnecessary repetitions which tend to increase your file size and slow down page load time. In React applications, React Router is used to enable easy navigation across pages. Rather than moving from the home page to the products page with an anchor tag that links both pages, you could use a Link component from the React Router Dom library to achieve the same result faster. In addition to being fast, using React Router Link prevents page refresh, which the anchor tag enables, thereby preserving your application states which could be wiped away when the page is refreshed in the case of an anchor tag.&lt;br&gt;
Authorization and authentication, though appear to have similar meanings, are different. Authentication is the validation of your entity while attempting entrance to a web application, while authorization is the access to features upon your entrance. For instance, you are checked at the front desk of a hotel after booking. The front desk clerk checks to confirm that you’re the one who booked a suite in the hotel by asking for a form of proof which could be a passcode—that is authentication. Upon identity verification at the front desk, you get a key or a card to access the room—authorization. Also, based on the suite type you paid for, you’re entitled to certain hospitality features and restricted from others—role-based authorization.&lt;br&gt;
With the prerequisite terms cleared, let’s proceed to implementing the routes.&lt;/p&gt;
&lt;h4 id=&quot;route-definition-explaining-the-routes-in-detail&quot;&gt;Route Definition: Explaining the routes in detail&lt;/h4&gt;
&lt;p&gt;Before we dive into protected routes, it's important to understand the distinction between different routes in a React application. Here's a table that clarifies the differences between public, private, and protected routes:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=16244291999271157374&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Understanding these distinctions will help you implement the appropriate routing strategy for your application.&lt;/p&gt;
&lt;h4 id=&quot;protected-route&quot;&gt;Protected Route&lt;/h4&gt;
&lt;p&gt;Any page that you cannot access without authentication is a protected route. To implement a protected route in your application, you need to monitor the logged-in state of your user to know when to allow the user to access the page or redirect the user to the login page. The common page access authorization you get upon authentication is the token. The token is similar to the room key or card you get in a hotel upon identity verification, and in a web application, it is stored in the &lt;code&gt;localStorage&lt;/code&gt; in most web applications. However, we'll use HTTP-only cookies to store authentication tokens instead of &lt;code&gt;localStorage&lt;/code&gt; for enhanced security. Opposed to using &lt;code&gt;localStorage&lt;/code&gt;, this approach helps protect against XSS attacks. The presence of the token in the browser cookies tells you that your user is authenticated, and the absence tells you the reverse—the user has logged out of the application. With this detail, we can implement a protected route component.&lt;br&gt;
First and foremost, let’s create a React app and install React Router. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm create vite@latest authorization-routes -- --template //vite
npm i react-router-dom // React Router
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cd into your app’s directory and open it in your code editor.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd authorization-routes
code .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Encapsulate your application’s route in React router by wrapping the &lt;code&gt;BrowserRouter&lt;/code&gt;  component from &lt;code&gt;react-router-dom&lt;/code&gt; around the &lt;code&gt;AllRoutes.jsx&lt;/code&gt; file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;App.jsx

import { BrowserRouter } from &quot;react-router-dom&quot;;

export default function App() {
  return (
    &amp;lt;BrowserRouter&amp;gt;
      &amp;lt;AllRoutes /&amp;gt;
    &amp;lt;/BroswerRouter&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, create a useAuth hook to handle the users’ logged-in state and roles. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useState, useEffect } from 'react';

export function useAuth() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [userRole, setUserRole] = useState(null);

  useEffect(() =&amp;gt; {
    const checkAuthStatus = async () =&amp;gt; {
      try {
        // Note: Ensure this endpoint is implemented on your server
        // It should validate the token (e.g., check expiration, decode payload)
        const response = await fetch('/api/auth/status', { credentials: 'include' });
        
        if (response.ok) {
          const data = await response.json();
          setIsLoggedIn(true);
          setUserRole(data.role);
        } else {
          // If the response is not OK, assume the user is not logged in
          setIsLoggedIn(false);
          setUserRole(null);
        }
      } catch (error) {
        console.error('Error checking auth status:', error);
        // In case of an error, assume the user is not logged in
        setIsLoggedIn(false);
        setUserRole(null);
      }
    };

    checkAuthStatus();

    // You might want to add a cleanup function or re-fetch logic here
    // depending on your application's needs
  }, []);

  return { isLoggedIn, userRole };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The useAuth hook is a custom React hook designed to manage the authentication state in our application. &lt;/p&gt;
&lt;p&gt;Now, set up your &lt;code&gt;AllRoutes.jsx&lt;/code&gt; page and implement the ProtectedRoute.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AllRoutes.jsx

import { Routes, Route, Navigate, useLocation } from &quot;react-router-dom&quot;;

import { useAuth } from &quot;./hooks/useAuth&quot;;
import LoginPage from &quot;./pages/Auth/LoginPage&quot;;
import SignupPage from &quot;./pages/Auth/SignupPage&quot;;
import DashboardPage from &quot;./pages/Dashboard&quot;;
import ProfilePage from &quot;./pages/Dashboard/ProfilePage&quot;;

export default function AllRoutes() {
  const location = useLocation();
  const { isLoggedIn } = useAuth();

  const ProtectedRoute = ({ children }) =&amp;gt; {
    const { isLoggedIn } = useAuth();
    const location = useLocation();

    if (!isLoggedIn) {
      return &amp;lt;Navigate to=&quot;/login&quot; state={{ from: location }} replace /&amp;gt;;
    }
    return children;
  };
  return (
    &amp;lt;Routes&amp;gt;
      &amp;lt;Route path=&quot;/login&quot; element={&amp;lt;LoginPage /&amp;gt;} /&amp;gt;
      &amp;lt;Route path=&quot;/sign-up&quot; element={&amp;lt;SignupPage /&amp;gt;} /&amp;gt;
      &amp;lt;Route path=&quot;/user&quot; element={&amp;lt;ProtectedRoute /&amp;gt;}&amp;gt;
        &amp;lt;Route path=&quot;dashboard&quot; element={&amp;lt;DashboardPage /&amp;gt;} /&amp;gt;
        &amp;lt;Route path=&quot;profile&quot; element={&amp;lt;ProfilePage /&amp;gt;} /&amp;gt;
        {/* Other protected routes */}
      &amp;lt;/Route&amp;gt;
    &amp;lt;/Routes&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just as stated earlier, the protected route component follows a simple principle. It checks if a user is currently authenticated based on the presence of the token in the &lt;code&gt;localStorage&lt;/code&gt; API. If the token is present, the user is authorized to access every route without role-based authorization. If not, the user is redirected to the login page to get authenticated. &lt;br&gt;
The &lt;code&gt;replace&lt;/code&gt; prop in the &lt;code&gt;Navigate&lt;/code&gt; component ensures that the user cannot access the last authenticated page by clicking the back button on the browser.&lt;/p&gt;
&lt;h4 id=&quot;private-route&quot;&gt;Private Route&lt;/h4&gt;
&lt;p&gt;The Private component follows a similar paradigm to the protected route but with an extra layer of checks. In addition to checking if the user is authenticated, it checks for the user role before granting them access to the page.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AllRoutes.jsx

import { Routes, Route, Navigate, useLocation } from &quot;react-router-dom&quot;;

import { useAuth } from &quot;./hooks/useAuth&quot;;
import LoginPage from &quot;./pages/Auth/LoginPage&quot;;
import SignupPage from &quot;./pages/Auth/SignupPage&quot;;
import DashboardPage from &quot;./pages/Dashboard&quot;;
import ProfilePage from &quot;./pages/Dashboard/ProfilePage&quot;;
import UnauthorizedPage from &quot;./pages/UnauthorizedPage&quot;;
import NotFoundPage from &quot;./pages/NotFoundPage&quot;;

export default function AllRoutes() {
  const location = useLocation();
  const { isLoggedIn, userRole } = useAuth();

  const ProtectedRoute = ({ children }) =&amp;gt; {
    const { isLoggedIn } = useAuth();
    const location = useLocation();

    if (!isLoggedIn) {
      return &amp;lt;Navigate to=&quot;/login&quot; state={{ from: location }} replace /&amp;gt;;
    }
    return children;
  };

  const PrivateRoute = ({ children, allowedRoles = [] }) =&amp;gt; {
    const { isLoggedIn, userRole } = useAuth();
    const location = useLocation();

    if (!isLoggedIn) {
      return &amp;lt;Navigate to=&quot;/login&quot; state={{ from: location }} replace /&amp;gt;;
    }

    if (allowedRoles.length &amp;amp;&amp;amp; !allowedRoles.includes(userRole)) {
      return &amp;lt;Navigate to=&quot;/unauthorized&quot; replace /&amp;gt;;
    }

    return children;
  };
  return (
    &amp;lt;Routes&amp;gt;
      &amp;lt;Route path=&quot;/login&quot; element={&amp;lt;LoginPage /&amp;gt;} /&amp;gt;
      &amp;lt;Route path=&quot;/sign-up&quot; element={&amp;lt;SignupPage /&amp;gt;} /&amp;gt;
      {/* Protected routes */}
      &amp;lt;Route path={`/${userRole}`} element={&amp;lt;ProtectedRoute /&amp;gt;}&amp;gt;
        &amp;lt;Route path=&quot;dashboard&quot; element={&amp;lt;DashboardPage /&amp;gt;} /&amp;gt;
        &amp;lt;Route path=&quot;profile&quot; element={&amp;lt;ProfilePage /&amp;gt;} /&amp;gt;
      &amp;lt;/Route&amp;gt;
      {/* Buyer's routes */}
      &amp;lt;Route path={`/${userRole}`} element={&amp;lt;PrivateRoute 
      allowedRoles={[&quot;buyer&quot;]} /&amp;gt;}&amp;gt;
        &amp;lt;Route path=&quot;cart&quot; element={&amp;lt;CartPage /&amp;gt;} /&amp;gt;
        &amp;lt;Route path=&quot;wishlist&quot; element={&amp;lt;WishlistPage /&amp;gt;} /&amp;gt;
      &amp;lt;/Route&amp;gt;
      {/* Seller's routes */}
      &amp;lt;Route path={`/${userRole}`} element={&amp;lt;PrivateRoute 
      allowedRole={[&quot;seller&quot;]} /&amp;gt;}&amp;gt;
        &amp;lt;Route path=&quot;post-product&quot; element={&amp;lt;PostProduct /&amp;gt;} /&amp;gt;
        &amp;lt;Route path=&quot;all-products&quot; element={&amp;lt;AllProductsPage/&amp;gt;} /&amp;gt;
      &amp;lt;/Route&amp;gt;
      {/* Error handling routes */}
      &amp;lt;Route path=&quot;/unauthorized&quot; element={&amp;lt;UnauthorizedPage /&amp;gt;} /&amp;gt;
      &amp;lt;Route path=&quot;*&quot; element={&amp;lt;NotFoundPage /&amp;gt;} /&amp;gt;
    &amp;lt;/Routes&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An extra layer of scrutiny has been added to check the roles and give users access to the pages. The Private route takes 2 props—the children (the pages to be returned if the check is passed) and the role. The component checks if the user is logged in and the role passed matches the role returned by the useAuth hook. If the checks return &lt;code&gt;true&lt;/code&gt;, the user gets access to the page; if not, the user is redirected to the login page.&lt;/p&gt;
&lt;h4 id=&quot;public-route&quot;&gt;Public Route&lt;/h4&gt;
&lt;p&gt;Although the public routes sound like those that should not need a special component, but when you realize how funny your users might be, you see a need to implement one. Logged-in users could decide to clear their current path in the browser and replace it with “/” to get to the home page rather than log out, and if your application allows such, it’s a terrible user experience practice. A public route component prevents logged-in users from accessing unauthenticated pages in the application by redirecting them to the current page they are on until they log out.&lt;br&gt;
The Public route component contrasts the Protected and Private route components. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AllRoutes.jsx

import { Routes, Route, Navigate, useLocation } from &quot;react-router-dom&quot;;

import { useAuth } from &quot;./hooks/useAuth&quot;;
import LoginPage from &quot;./pages/Auth/LoginPage&quot;;
import SignupPage from &quot;./pages/Auth/SignupPage&quot;;
import DashboardPage from &quot;./pages/Dashboard&quot;;
import ProfilePage from &quot;./pages/Dashboard/ProfilePage&quot;;

export default function AllRoutes() {
  const location = useLocation();
  const { isLoggedIn, userRole } = useAuth();

  // Protected Route
  const ProtectedRoute = ({ children }) =&amp;gt; {
    const { isLoggedIn } = useAuth();
    const location = useLocation();

    if (!isLoggedIn) {
      return &amp;lt;Navigate to=&quot;/login&quot; state={{ from: location }} replace /&amp;gt;;
    }
    return children;
  };

  // Private Route
  const PrivateRoute = ({ children, allowedRoles = [] }) =&amp;gt; {
    const { isLoggedIn, userRole } = useAuth();
    const location = useLocation();

    if (!isLoggedIn) {
      return &amp;lt;Navigate to=&quot;/login&quot; state={{ from: location }} replace /&amp;gt;;
    }

    if (allowedRoles.length &amp;amp;&amp;amp; !allowedRoles.includes(userRole)) {
      return &amp;lt;Navigate to=&quot;/unauthorized&quot; replace /&amp;gt;;
    }

    return children;
  };

  // Public Route
  const PublicRoute = ({ children }) =&amp;gt; {
    const { isLoggedIn, userRole } = useAuth();
  
    if (isLoggedIn) {
      return &amp;lt;Navigate to={`/${userRole}/dashboard`} replace /&amp;gt;;
    }
    return children;
  };

  return (
    &amp;lt;Routes&amp;gt;
      {/* Public Path */}
      &amp;lt;Route path=&quot;/&quot; element={&amp;lt;PublicRoute /&amp;gt;}&amp;gt;
        &amp;lt;Route path=&quot;login&quot; element={&amp;lt;LoginPage /&amp;gt;} /&amp;gt;
        &amp;lt;Route path=&quot;sign-up&quot; element={&amp;lt;SignupPage /&amp;gt;} /&amp;gt;
      &amp;lt;/Route&amp;gt;
      {/* Protected routes */}
      &amp;lt;Route path={`/${userRole}`} element={&amp;lt;ProtectedRoute /&amp;gt;}&amp;gt;
        &amp;lt;Route path=&quot;dashboard&quot; element={&amp;lt;DashboardPage /&amp;gt;} /&amp;gt;
        &amp;lt;Route path=&quot;profile&quot; element={&amp;lt;ProfilePage /&amp;gt;} /&amp;gt;
      &amp;lt;/Route&amp;gt;
      {/* Buyer's routes */}
      &amp;lt;Route path={`/${userRole}`} element={&amp;lt;PrivateRoute 
      allowedRoles={[&quot;buyer&quot;]} /&amp;gt;}&amp;gt;
        &amp;lt;Route path=&quot;cart&quot; element={&amp;lt;CartPage /&amp;gt;} /&amp;gt;
        &amp;lt;Route path=&quot;wishlist&quot; element={&amp;lt;WishlistPage /&amp;gt;} /&amp;gt;
      &amp;lt;/Route&amp;gt;
      {/* Seller's routes */}
      &amp;lt;Route path={`/${userRole}`} element={&amp;lt;ProtectedRoute 
      allowedRole={[&quot;seller&quot;]} /&amp;gt;}&amp;gt;
        &amp;lt;Route path=&quot;post-product&quot; element={&amp;lt;PostProduct /&amp;gt;} /&amp;gt;
        &amp;lt;Route path=&quot;all-products&quot; element={&amp;lt;AllProductsPage/&amp;gt;} /&amp;gt;
      &amp;lt;/Route&amp;gt;
      {/* Error handling routes */}
      &amp;lt;Route path=&quot;/unauthorized&quot; element={&amp;lt;UnauthorizedPage /&amp;gt;} /&amp;gt;
      &amp;lt;Route path=&quot;*&quot; element={&amp;lt;NotFoundPage /&amp;gt;} /&amp;gt;
    &amp;lt;/Routes&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whenever a user attempts to navigate to any public routes without logging out, the Public route component gets the current pathname and redirects the user to the path if the user is logged in. If the user is not logged in, the user can easily navigate to the unauthenticated pages.&lt;/p&gt;
&lt;p&gt;We've defined three types of route components to handle different access levels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PublicRoute&lt;/code&gt;: This component is used for pages that should be accessible to all users, such as the home page, login page, and signup page. If a logged-in user tries to access these routes, they'll be redirected to their dashboard.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ProtectedRoute&lt;/code&gt;: This component is used for pages that require authentication but are accessible to all authenticated users, regardless of their role. The dashboard and profile pages are examples of protected routes.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PrivateRoute&lt;/code&gt;: This component is used for role-specific pages. It checks if the user is authenticated and also verifies if the user's role is included in the &lt;code&gt;allowedRoles&lt;/code&gt; array. This allows for flexible role-based access control.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using these components, we can easily control access to different parts of our application based on authentication status and user roles.&lt;/p&gt;
&lt;h4 id=&quot;real-world-implications-of-route-types&quot;&gt;Real-World Implications of Route Types&lt;/h4&gt;
&lt;p&gt;Understanding the practical applications and implications of different route types is crucial for building secure and user-friendly React applications. Let's explore each type in more detail:&lt;/p&gt;
&lt;p&gt;Public routes are commonly used in landing pages, about pages, pricing information, and login and registration pages. Without proper PublicRoute implementation, logged-in users might face confusing situations. For instance, if a user bookmarks the login page and accesses it later while already logged in, they could see the login form again instead of their dashboard. This creates a poor user experience and might lead to unnecessary logout/login cycles. &lt;/p&gt;
&lt;p&gt;Protected routes are used for user dashboards, account settings pages, and basic features available to authenticated users. Failing to implement protected routes could lead to security vulnerabilities. Unauthenticated users might gain access to sensitive information or functionality. For example, if a user's session expires while browsing their account settings, they should be immediately redirected to the login page to authenticate. This ensures the security of their personal information.&lt;/p&gt;
&lt;p&gt;Private routes are used for role-based pages like admin panels for admins, seller dashboards (in e-commerce applications), and premium features available to paying users. Without private routes, your application might expose functionality or information to users who shouldn't have access. This not only creates security risks but also leads to a confusing user experience. For instance, in an e-commerce platform, a regular buyer shouldn't see options to add or edit product listings, which should be restricted to seller accounts.&lt;/p&gt;
&lt;p&gt;By implementing these route types correctly, you ensure that users are guided to the appropriate parts of your application based on their authentication status and role, sensitive information and functionality are protected from unauthorized access, and the user interface remains clean and relevant to their permissions and needs.&lt;/p&gt;
&lt;h4 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;In the article, you’ve learned how to implement &lt;strong&gt;Private&lt;/strong&gt;, &lt;strong&gt;Protected&lt;/strong&gt;, and &lt;strong&gt;Public&lt;/strong&gt; routes and also their differences. These explicit route components provide a clear structure for implementing different levels of access control in your React application. They make it easy to manage public, protected, and role-specific routes, improving the security and organization of your application. &lt;br&gt;
Also, you’ve touched a bit of &lt;strong&gt;authorization&lt;/strong&gt; and &lt;strong&gt;authentication&lt;/strong&gt;. For a better understanding of these concepts see my article on the topic: &lt;a rel=&quot;nofollow&quot; href=&quot;https://drprime.hashnode.dev/authentication-vs-authorization&quot;&gt;https://drprime.hashnode.dev/authentication-vs-authorization&lt;/a&gt;.&lt;/p&gt;
</description>
<category>JavaScript</category>
<guid isPermaLink="true">https://coderlegion.com/396/learn-how-to-implement-public-private-and-protected-routes-in-your-react-application</guid>
<pubDate>Thu, 03 Oct 2024 21:10:25 +0000</pubDate>
</item>
</channel>
</rss>