Работа с Jotai

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 маршрута загружает данные асинхронно, а затем сохраняет их в атом, что позволяет компоненту автоматически обновляться при изменении состояния.


Реактивное связывание состояния маршрутов с Jotai

В большинстве случаев важно, чтобы маршрут и состояние атома оставались синхронизированы. Для этого 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) позволяет получить данные маршрута и параметры.
  • Атом Jotai автоматически обновляет компонент при изменении состояния.
  • 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

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 (
    
{ e.preventDefault(); handleSubmit(); }}> setForm(prev => ({ ...prev, title: e.target.value }))} />
); }

Использование атомов для формы гарантирует, что состояние не теряется при переходе между маршрутами, пока атом не будет сброшен вручную.


Синхронизация глобального состояния с маршрутом

Для глобальных состояний, таких как авторизация или настройки пользователя, рекомендуется использовать глобальные атомы, которые не зависят от конкретного маршрута:

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;
  },
});

Это позволяет маршрутизатору учитывать состояние авторизации и управлять доступом к страницам.