TanStack Router предоставляет мощные инструменты для организации маршрутизации в приложениях на React, а Jotai обеспечивает простую и реактивную работу с состоянием. Совместное использование этих библиотек позволяет создавать приложения с централизованным управлением состоянием маршрутов, а также с реактивной синхронизацией состояния UI.
Для начала необходимо определить маршруты через
createRouter из TanStack Router:
import { createRouter, Route } from '@tanstack/router';
import { atom } from 'jotai';
import { useAtom } from 'jotai/react';
const userAtom = atom({ id: null, name: '' });
const homeRoute = new Route({
path: '/',
component: Home,
});
const profileRoute = new Route({
path: '/profile/:userId',
component: Profile,
loader: async ({ params }) => {
// загрузка данных пользователя на основе Jotai атома
const [user, setUser] = useAtom(userAtom);
const response = await fetch(`/api/users/${params.userId}`);
const data = await response.json();
setUser(data);
return data;
},
});
const router = createRouter({
routeTree: homeRoute.addChildren([profileRoute]),
});
Ключевой момент здесь — использование Jotai атомов для хранения загруженных данных. Loader маршрута загружает данные асинхронно, а затем сохраняет их в атом, что позволяет компоненту автоматически обновляться при изменении состояния.
В большинстве случаев важно, чтобы маршрут и состояние атома
оставались синхронизированы. Для этого TanStack Router
позволяет использовать хуки useMatch и
useNavigate:
import { useMatch, useNavigate } from '@tanstack/router';
function Profile() {
const match = useMatch(profileRoute);
const [user, setUser] = useAtom(userAtom);
const navigate = useNavigate();
const handleLogout = () => {
setUser({ id: null, name: '' });
navigate({ to: homeRoute });
};
return (
{user.name}
);
}
Ключевые моменты:
useMatch(route) позволяет получить данные маршрута и
параметры.useNavigate позволяет управлять навигацией программно,
при этом реактивное состояние атома учитывается.Для динамических маршрутов важно использовать параметры маршрутов в атомах. Это позволяет разделять данные для разных маршрутов:
const profileAtomFamily = atom((param) => atom({ id: param.userId, name: '' }));
const profileRoute = new Route({
path: '/profile/:userId',
component: Profile,
loader: async ({ params }) => {
const userAtomInstance = profileAtomFamily(params);
const [user, setUser] = useAtom(userAtomInstance);
const data = await fetch(`/api/users/${params.userId}`).then(res => res.json());
setUser(data);
return data;
},
});
Использование atom family позволяет создавать атомы на основе параметров маршрута, что предотвращает конфликт состояния между разными страницами и пользователями.
TanStack Router предоставляет loader для асинхронной
загрузки данных, что отлично сочетается с атомами Jotai:
const postsAtom = atom([]);
const postsRoute = new Route({
path: '/posts',
component: Posts,
loader: async () => {
const [posts, setPosts] = useAtom(postsAtom);
const data = await fetch('/api/posts').then(res => res.json());
setPosts(data);
return data;
},
});
Такой подход позволяет разгрузить компоненты от прямого обращения к API и держать состояние в атомах.
Jotai идеально подходит для управления формами внутри маршрутов. Например:
const formAtom = atom({
title: '',
content: '',
});
function CreatePost() {
const [form, setForm] = useAtom(formAtom);
const navigate = useNavigate();
const handleSubmit = async () => {
await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(form),
});
navigate({ to: postsRoute });
};
return (
);
}
Использование атомов для формы гарантирует, что состояние не теряется при переходе между маршрутами, пока атом не будет сброшен вручную.
Для глобальных состояний, таких как авторизация или настройки пользователя, рекомендуется использовать глобальные атомы, которые не зависят от конкретного маршрута:
const authAtom = atom({ isLoggedIn: false, user: null });
const authRoute = new Route({
path: '/dashboard',
component: Dashboard,
loader: async () => {
const [auth, setAuth] = useAtom(authAtom);
const userData = await fetch('/api/me').then(res => res.json());
setAuth({ isLoggedIn: true, user: userData });
return userData;
},
});
Это позволяет маршрутизатору учитывать состояние авторизации и управлять доступом к страницам.