Skip to main content

Web Application Implementation Guide

This guide introduces recommended application implementation patterns using React Router v7 (Framework mode).

Implementing TODO List Display and State Update Functionality

We will implement the functionality to display a TODO List and update its state. While the implementation includes save button functionality after updating todos, this catalog doesn't perform actual database operations but simulates the process by showing loaders up to the point where such operations would occur.

  • Welcome page

    • /
  • Todo list page

    • /todolist

The catalog AMI already contains the implemented state, and this document allows you to trace through the flow of how it was implemented.

Prerequisites

Implementation methods for basic components like root.tsx and layout are partially omitted. However, the content of this catalog is almost identical to what's generated when creating a new React Router v7 project.

Overall Process Flow

As a recommended configuration, we will implement in the following order, assuming after the initial setup of the entire project.

Target FileImplementation Summary
src/types/todo.d.tsDefine Todo types
src/states/IsLoading.tsDefine data processing state
src/hooks/useTodoSaver.tsxDefine custom hook for saving Todos
src/components/TodoListItem.tsxImplement TodoList items as reusable components
src/pages/todolist/index.pages.tsxDefine TodoList page UI
src/routes/todolist.tsxTodoList page entry point and server-side processing (loader, action, screen entry point)
src/layouts/index.pages.tsxImplement app-wide layout
src/routes.tsDefine TodoList page routing

Additionally, please configure directories and files as needed.

src/types/todo.d.ts Define Todo Types

Define types as follows. These will be used for Props definitions in each component and type declarations for source data.

export type Todo = {
id: string
title: string
status: 'pending' | 'inProgress' | 'completed'
isStarred: boolean
// ...
}

Also, defining the following in src/types/index.ts simplifies import statements, so it's recommended to create this in all directories under src/.

export * from './Todo.d'

src/states/IsLoading.ts Define Data Processing State

Declare IsLoading state as follows. This catalog uses the Jotai library.

import { atom } from 'jotai'

export const IsLoadingAtom = atom<boolean>(false)

src/hooks/useTodoSaver.tsx Define Custom Hook for Saving Todos

Implement the process for saving Todo changes. While this catalog doesn't perform actual update processing, it implements sending updated Todos to PUT /todolist and logging the output.

import { useAtom } from 'jotai'
import * as Atoms from '~/states'

export const useTodoSaver = () => {
const [isLoading, setIsLoading] = useAtom(Atoms.IsLoadingAtom)

const saveTodo = async (todo: Todo) => {
setIsLoading(true)
const saveResult = fetch('/todolist', {
method: 'PUT',
headers: { 'content-type': 'application/json', body: JSON.stringify(todo) },
})
.then((res) => res.json())
.finally(() => setIsLoading(false))

console.log('## saveResult - ', saveResult)
}

return { isLoading, saveTodo }
}

src/components/TodoListItem.tsx Implement TodoList Items as Reusable Components

Implement Todo list items. Such list item components are positioned and created with reusability for other screens in mind.

import type { Todo } from '~/types'

type Props = {
todo: Todo
}

export const TodoListItem: React.FC<Props> = ({ todo }) => {

// Some dynamic ui handler methods
const onClickSave = async () => {
await saveTodo(todo).finally(() => {
toast('Info', {
description: `Sucessfully saved.`,
action: {
label: 'Close',
onClick: () => {
/* Closing toast. Nothing to do here. */
},
},
})
})
}

return (
<div className="bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md transition-shadow">
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-3">
<h3 className="text-lg font-medium text-gray-900 truncate">{todo.title}</h3>

{ /* Omitted for brevity. All sample code is included in the catalog AMI. */ }

</div>
</div>
</div>
</div>
)
}

src/pages/todolist/index.pages.tsx Define TodoList Page UI

Implement the UI for the TodoList page that displays the Todo list.

import { TodoListItem } from '~/components'
import type { Todo } from '~/types'

type Props = {
items: Todo[]
}

export const TodoListPage: React.FC<Props> = (props) => {
const { items } = props

return (
<>
<main className="max-w-6xl mx-auto px-4 py-8">

{ /* Omitted for brevity. All sample code is included in the catalog AMI. */ }

<div className="space-y-4">
{items.map((todo) => (
<TodoListItem key={todo.id} todo={todo} />
))}
</div>
</main>
</>
)
}

TodoList Entry Point and Server-side Processing Implementation

Under src/routes/, you can configure server-side processing (loader, action) and various settings like meta tags when loading the corresponding screen. It's possible to fetch data from the server side and pass it to the Page component from the previous section via Props or other means. You can also implement loaders on separate routes and take a SPA-like approach asynchronously with GET /api/todolist.

import type { MetaFunction } from 'react-router'
import type { Route } from './+types/todolist'

import type { Todo } from '~/types'
import { TODO_ITEMS } from '~/fixtures'
import { TodoListPage } from '~/pages'

export const meta: MetaFunction = () => {
// biome-ignore format: for better readability
return [
{ title: 'New React Router App - Todo List Page' },
{ name: 'description', content: 'Welcome to React Router!' }
]
}

export async function loader(params: Route.LoaderArgs) {
// For GET requests, this loader is called on the server side.
return { TODO_ITEMS }
}

export const action = async (_params: Route.ActionArgs) => {
// For POST, PUT, DELETE requests, this action is called on the server side.
// sleep here for 1 seconds for demo purpose
await sleep()

// Return normal API response
return new Response(JSON.stringify({ message: 'action is called' }), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
})


const TodoListRoute = ({ loaderData }: { loaderData: { TODO_ITEMS: Todo[] } }) => {
// Pass data retrieved from the loader to client-side components (under pages).
const { TODO_ITEMS } = loaderData
return <TodoListPage items={TODO_ITEMS} />
}

export default TodoListRoute

For details, please refer to the React Router v7 official documentation.

Implement App-wide Layout (Reflecting Loader)

Create src/layouts/default.ts and implement the layout applied to the entire application. You can appropriately specify which layout to define in the next section's routes.ts.

import { useAtom } from 'jotai'
import { Outlet, useLocation } from 'react-router'

import { Footer, Header, Loader } from '~/components'
import { Toaster } from '~/lib/shadcn-ui'
import * as Atoms from '~/states'

export const DefaultLayout = () => {
const [isLoading] = useAtom(Atoms.IsLoadingAtom)
const { pathname } = useLocation()

return (
<div className="min-h-screen">
<Header />

{ /* In this catalog, Loader is applied to layout */ }
{ /* Please place it in an appropriate component as needed. */ }
{isLoading && (
<div className="flex h-screen w-screen items-center justify-center overflow-scroll">
<Loader isLoading={isLoading} color="blue" />
</div>
)}

{!isLoading && (
<main key={pathname} className="min-h-screen">
<div className="bg-no-repeat bg-left-top">
{ /* Pages are reflected in the Outlet */ }
<Outlet />
</div>
</main>
)}

<Footer />
<Toaster />
</div>
)
}

export default DefaultLayout

TodoList Page Routing Definition

Finally, configure which routes file to route to when opening the screen. Here, create src/routes.ts and configure requests to /todolist to be routed to src/routes/todolist.tsx.

import { layout, type RouteConfig, route } from '@react-router/dev/routes'

const PREFIX = './routes'

export default [
// biome-ignore format: for better readability
layout('./layouts/default.tsx', [

// Welcome page
route('/', `${PREFIX}/welcome.tsx`),

// Todo list page
route('/todolist', `${PREFIX}/todolist.tsx`),

]),
] satisfies RouteConfig

Functionality Testing

Once you've progressed this far, launch the application in development mode and perform functionality testing. Proceed to local execution debugging and testing.