- 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
routesinside thesrcfolder - create a file named
__root.tsxinside theroutesfolder
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'spluginsarray - install TanStack Router CLI
npm i -g @tanstack/router-cli- run the TanStack Router CLI to generate
routeTree.gen.tsfile
tsr generateThe 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.tsfile
tsr watch- run the project
npm run dev- finish the
App.tsxfile to integrate the typesafe routes. - add an
index.tsxfile inside theroutesfolder. This file will be the entry point for the routes and boilerplate will be generated by the CLI for you. - update the
__root.tsxfile to add theOutletcomponent.
<>
<div id="detail">
<Outlet />
</div>
</>- see the changes in the browser
- update the
__root.tsxfile to add theTanStackRouterDevtoolscomponent.
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.tsxfile 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
Applicationtab - go to the indexedDB of the application
- go the web app and click the
Newbutton that will trigger thecreateContactfunction. - confirm that the new contact is added to the indexedDB
- update the
RootRouteOptionsof the__root.tsxfile.
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
Routeinstance from the__root.tsxfile.
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.tsxinside theroutesfolder then save the file to reload your IDE. - check the
routeTree.gen.tsfile. It should have the new route. - change the
ahref tagto use theLinkcomponent 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
Linkwith 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.tsxfile 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
ContactDetailcomponent like this:
function ContactIdIndexComponent() {
return (<ContactDetail />);
}- Still in the same file, update the component prop of the Route instance to use the
ContactIdIndexComponentfunction.
component: ContactIdIndexComponent,- go to the
ContactDetail.tsxand replace the contact placeholder with theRouteinstance from thecontacts.$contactId.index.tsxfile.
const contact = Route.useLoaderData();- replace the params placeholder with this:
const params = Route.useParams();- add the the Route's
useNavigatehook 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.tsxinside theroutesfolder then save the file to reload your IDE. - update the
contacts.$contactId.edit.tsxfile 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
EditContactFormcomponent like this:
function EditContactComponent() {
return (<EditContactForm />);
}- Still in the same file, update the component prop of the Route instance to use the
EditContactComponentfunction.
component: EditContactComponent,- we will need again the 3 Route hooks. Place them in the
EditContactForm.tsxfile.
const contact = Route.useLoaderData();
const params = Route.useParams();
const navigate = Route.useNavigate();- update the
handleOnSubmitfunction 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
/editto 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.tsxfile and update thehandleEditEventfunction with this:
const handleEditEvent = async (event: FormEvent) => {
event.preventDefault();
await navigate({
to: `/contacts/${params.contactId}/edit`,
});
};- go back also to the
SidebarSearchContact.tsxfile. Import theRoutefrom the__root.tsxand add this hook to the component.
const navigate = Route.useNavigate();- update the
handleOnSubmitwith this logic.
const contact = await createContact();
await navigate({
to: `/contacts/${contact.id}/edit`,
});- add this inside the
onClickevent 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.tsxfile and the hooks:
const { q } = Route.useLoaderData();
const [query, setQuery] = useState(q ?? '');
const router = useRouter();
useEffect(() => {
if (q) setQuery(q);
}, [q]);- update the
divtag of the<Outlet />with this:
<div id="detail" className={router.state.isLoading ? 'loading' : ''}>
<Outlet />
</div>- add
queryandsetQueryprops to the SidebarSearchContact component.
<SidebarSearchContact query={query} setQuery={setQuery} />- go to the
SidebarSearchContact.tsxfile and add the props to the component.
function SidebarSearchContact({ query, setQuery }: Props)- update the
handleOnChangeEventfunction with this:
const handleOnChangeEvent = async (e: FormEvent<HTMLInputElement>) => {
setQuery(e.currentTarget.value);
await navigate({ search: { q: e.currentTarget.value } });
};- import the
useRouterhook to the component.
const router = useRouter();- add two more properties to the
inputtag.
value={query}
className={router.state.isLoading ? 'loading' : ''}- replace the hidden property of the
divtag 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.tsxfile and add theuseRouterhook.
const router = useRouter();- then update the
onSubmitfunction with this after or below theupdateContactfunction. 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! 🚀