- This is originated in React Router Tutorial
- data is saved in indexedDB in the browser.
- you can use any package manager you like
- download the starter repo
- npm install
- create a folder named
routes
inside thesrc
folder - create a file named
__root.tsx
inside theroutes
folder
import { createRootRoute } from "@tanstack/react-router";
export const Route = createRootRoute({
component: RootComponent
});
function RootComponent() {
return (
<div>
<h1>Hello from React and TanStack Router</h1>
</div>
);
}
- import and add
TanStackRouterVite()
in vite.config.ts'splugins
array - install TanStack Router CLI
npm i -g @tanstack/router-cli
- run the TanStack Router CLI to generate
routeTree.gen.ts
file
tsr generate
The tsr generate command will generate a routeTree.gen.ts
file in the src
folder. Normally in React Router, we would have to manually create the routes and nested routes. But with TanStack Router, we can generate the routes and nested routes using the CLI.
- run the tsr watcher to watch for changes in the
routeTree.gen.ts
file
tsr watch
- run the project
npm run dev
- finish the
App.tsx
file to integrate the typesafe routes. - add an
index.tsx
file inside theroutes
folder. This file will be the entry point for the routes and boilerplate will be generated by the CLI for you. - update the
__root.tsx
file to add theOutlet
component.
<>
<div id="detail">
<Outlet />
</div>
</>
- see the changes in the browser
- update the
__root.tsx
file to add theTanStackRouterDevtools
component.
const TanStackRouterDevtools =
process.env.NODE_ENV === 'production'
? () => null // Render nothing in production
: lazy(() =>
// Lazy load in development
import('@tanstack/router-devtools').then((res) => ({
default: res.TanStackRouterDevtools,
// For Embedded Mode
// default: res.TanStackRouterDevtoolsPanel
}))
);
- then above the RootComponent function, add the following code:
<>
<div id="detail">
<Outlet />
</div>
<Suspense>
<TanStackRouterDevtools position="bottom-right" />
</Suspense>
</>
- see the changes in the browser
- update the
__root.tsx
file to add sidebar and the list of contacts.
<>
<div id="sidebar">
<SidebarFooter />
</div>
<div id="detail">
<Outlet />
</div>
<Suspense>
<TanStackRouterDevtools position="bottom-right" />
</Suspense>
</>
- import and place the
<SidebarSearchContact />
below the SideBarFooter component.
<SidebarSearchContact />
- open the chrome devtools and go to the
Application
tab - go to the indexedDB of the application
- go the web app and click the
New
button that will trigger thecreateContact
function. - confirm that the new contact is added to the indexedDB
- update the
RootRouteOptions
of the__root.tsx
file.
export const Route = createRootRoute({
component: RootComponent,
validateSearch: z.object({
q: z.string().optional(),
}),
// eslint-disable-next-line sort-keys-fix/sort-keys-fix
loaderDeps: ({ search: { q } }) => {
return { q };
},
// eslint-disable-next-line sort-keys-fix/sort-keys-fix
loader: async ({ deps: { q } }) => {
const contacts = (await getContacts(q || '')) as Contact[];
return { contacts, q };
},
});
- add the
<SidebarContactList />
below the SidebarSearchContact component.
<SidebarContactList />
- see the changes in the browser. It says no contacts.
- replace the placeholder of contacts with the
Route
instance from the__root.tsx
file.
const { contacts } = Route.useLoaderData();
- see the changes in the browser. It should now show the list of contacts with one object (no name).
- create a new page named
contacts.$contactId.index.tsx
inside theroutes
folder then save the file to reload your IDE. - check the
routeTree.gen.ts
file. It should have the new route. - change the
ahref tag
to use theLink
component from TanStack Router.
<Link to={`/contacts/`}></Link>
- hover over the to prop and see the type of the prop. It should be a set of union type of strings.
- update the
Link
with this.
<Link to={`/contacts/${contact.id}`}></Link>
- go to the browser and click on the contact. It should navigate to the empty contact detail page.
- update the
contacts.$contactId.index.tsx
file to show the contact details.
import { createFileRoute, notFound } from '@tanstack/react-router';
import { z } from 'zod';
import { getContact } from '../services/contacts';
import ContactDetail from '../components/ContactDetail';
import NotFoundPage from '../components/NotFoundPage';
import ErrorPage from '../components/ErrorPage';
export const Route = createFileRoute('/contacts/$contactId/')({
component: () => <div>Hello /contacts/$contactId/!</div>,
notFoundComponent: () => <NotFoundPage message={"Can't find contact"} />,
errorComponent: () => <ErrorPage message={'Network error'} />,
params: {
parse: (params) => {
return {
contactId: z.string().parse(params.contactId),
};
},
stringify: ({ contactId }) => {
return { contactId: `${contactId}` };
},
},
// eslint-disable-next-line sort-keys-fix/sort-keys-fix
loader: async ({ params: { contactId } }) => {
const contact = await getContact(contactId as string);
if (!contact) {
throw notFound({ _global: false });
}
return contact;
},
});
- In the same file, separate the component and import the
ContactDetail
component like this:
function ContactIdIndexComponent() {
return (<ContactDetail />);
}
- Still in the same file, update the component prop of the Route instance to use the
ContactIdIndexComponent
function.
component: ContactIdIndexComponent,
- go to the
ContactDetail.tsx
and replace the contact placeholder with theRoute
instance from thecontacts.$contactId.index.tsx
file.
const contact = Route.useLoaderData();
- replace the params placeholder with this:
const params = Route.useParams();
- add the the Route's
useNavigate
hook to the component.
const navigate = Route.useNavigate();
- update the handleDelete function to use the params and navigate function.
const handleDeleteEvent = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (globalThis.confirm('Please confirm you want to delete this record.')) {
await deleteContact(params.contactId);
await navigate({
to: '/',
});
}
};
- go to the browser and click on the delete button. It should delete the contact and navigate to the home page.
- add more contacts and delete them to see the changes in the browser.
- create a new page named
contacts.$contactId.edit.tsx
inside theroutes
folder then save the file to reload your IDE. - update the
contacts.$contactId.edit.tsx
file to fetch the contact details.
import { createFileRoute, notFound } from '@tanstack/react-router';
import { getContact } from '../services/contacts';
import EditContactForm from '../components/EditContactForm';
export const Route = createFileRoute('/contacts/$contactId/edit')({
component: () => <div>Hello /contacts/$contactId/edit!</div>,
loader: async ({ params: { contactId } }) => {
const contact = await getContact(contactId);
if (!contact) {
throw notFound({ _global: false });
}
return contact;
},
});
- In the same file, separate the component and import the
EditContactForm
component like this:
function EditContactComponent() {
return (<EditContactForm />);
}
- Still in the same file, update the component prop of the Route instance to use the
EditContactComponent
function.
component: EditContactComponent,
- we will need again the 3 Route hooks. Place them in the
EditContactForm.tsx
file.
const contact = Route.useLoaderData();
const params = Route.useParams();
const navigate = Route.useNavigate();
- update the
handleOnSubmit
function to the params and the navigate.
const handleOnSubmit = async (event: FormEvent) => {
event.preventDefault();
const form = event.currentTarget as HTMLFormElement;
const formData = new FormData(form);
const updates = Object.fromEntries(formData.entries());
await updateContact(params.contactId as string, updates);
await navigate({
to: `/contacts/${params.contactId}`,
});
};
- Go the browser and click a no name contact. Add a
/edit
to the url and see the edit form. - Try to update the details of the contact and see the changes in the browser.
- go back to the
ContactDetail.tsx
file and update thehandleEditEvent
function with this:
const handleEditEvent = async (event: FormEvent) => {
event.preventDefault();
await navigate({
to: `/contacts/${params.contactId}/edit`,
});
};
- go back also to the
SidebarSearchContact.tsx
file. Import theRoute
from the__root.tsx
and add this hook to the component.
const navigate = Route.useNavigate();
- update the
handleOnSubmit
with this logic.
const contact = await createContact();
await navigate({
to: `/contacts/${contact.id}/edit`,
});
- add this inside the
onClick
event of the cancel button.
navigate({
to: `/contacts/${contact.id}`,
})
- go to the browser and click the New button. It should navigate to the edit form of the new contact.
- go to the
__root.tsx
file and the hooks:
const { q } = Route.useLoaderData();
const [query, setQuery] = useState(q ?? '');
const router = useRouter();
useEffect(() => {
if (q) setQuery(q);
}, [q]);
- update the
div
tag of the<Outlet />
with this:
<div id="detail" className={router.state.isLoading ? 'loading' : ''}>
<Outlet />
</div>
- add
query
andsetQuery
props to the SidebarSearchContact component.
<SidebarSearchContact query={query} setQuery={setQuery} />
- go to the
SidebarSearchContact.tsx
file and add the props to the component.
function SidebarSearchContact({ query, setQuery }: Props)
- update the
handleOnChangeEvent
function with this:
const handleOnChangeEvent = async (e: FormEvent<HTMLInputElement>) => {
setQuery(e.currentTarget.value);
await navigate({ search: { q: e.currentTarget.value } });
};
- import the
useRouter
hook to the component.
const router = useRouter();
- add two more properties to the
input
tag.
value={query}
className={router.state.isLoading ? 'loading' : ''}
- replace the hidden property of the
div
tag with an idid="search-spinner"
with this:
hidden={!router.state.isLoading}
- go to the browser and type a name in the search bar. It should show the loading spinner and the list of contacts.
- go to the
Favorite.tsx
file and add theuseRouter
hook.
const router = useRouter();
- then update the
onSubmit
function with this after or below theupdateContact
function. The invalidation will trigger the loader to fetch the data again.:
await router.invalidate();
- Thank you for following the steps. You can now explore more of the TanStack Router features and functionalities.
- You can also check the TanStack Router documentation for more information.
- Happy coding! 🚀