Настройка State Management (Jotai) для React-приложения

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

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка State Management (Jotai) для React-приложения
Простая
от 4 часов до 2 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Настройка State Management (Jotai) для React-приложения

Jotai строит состояние из атомов — маленьких изолированных единиц, которые можно комбинировать. Ближайшая аналогия — Recoil, но без boilerplate и с лучшей TypeScript-поддержкой из коробки. Компонент подписывается только на те атомы, которые читает, и ре-рендерится только при их изменении.

Хорошо подходит для приложений с большим количеством независимых кусков состояния: форм, UI-состояния, кэша запросов.

Что входит в работу

Настройка атомарной структуры состояния под проект: базовые атомы, производные атомы, async атомы, интеграция с React Suspense, подключение devtools, организация файлов.

Установка

npm install jotai
# опционально — утилиты
npm install jotai-devtools

Базовые атомы

import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'

// примитивный атом
export const countAtom = atom(0)

// производный атом (readonly)
export const doubleCountAtom = atom((get) => get(countAtom) * 2)

// производный атом с записью
export const incrementAtom = atom(
  (get) => get(countAtom),
  (get, set, step: number = 1) => set(countAtom, get(countAtom) + step)
)
function Counter() {
  const [count, setCount] = useAtom(countAtom)
  const double = useAtomValue(doubleCountAtom)
  const increment = useSetAtom(incrementAtom)

  return (
    <div>
      <span>{count} (×2 = {double})</span>
      <button onClick={() => increment(1)}>+1</button>
      <button onClick={() => setCount(0)}>Сброс</button>
    </div>
  )
}

useAtomValue — подписка только на чтение, не возвращает сеттер. useSetAtom — только сеттер, компонент не ре-рендерится при изменении атома.

Async атомы и Suspense

import { atom } from 'jotai'

const userIdAtom = atom<number | null>(null)

// async производный атом
export const userAtom = atom(async (get) => {
  const id = get(userIdAtom)
  if (!id) return null
  const res = await fetch(`/api/users/${id}`)
  if (!res.ok) throw new Error('Пользователь не найден')
  return res.json() as Promise<User>
})
// компонент оборачивается в Suspense
function UserProfile() {
  const user = useAtomValue(userAtom) // suspend до загрузки
  if (!user) return <p>Выберите пользователя</p>
  return <p>{user.name}</p>
}

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <ErrorBoundary fallback={<Error />}>
        <UserProfile />
      </ErrorBoundary>
    </Suspense>
  )
}

Атом с записью для форм

interface FormState {
  name: string
  email: string
  errors: Record<string, string>
}

const formAtom = atom<FormState>({
  name: '',
  email: '',
  errors: {},
})

// производный атом-экшен
export const submitFormAtom = atom(null, async (get, set) => {
  const form = get(formAtom)
  const errors: Record<string, string> = {}

  if (!form.name) errors.name = 'Обязательное поле'
  if (!form.email.includes('@')) errors.email = 'Некорректный email'

  if (Object.keys(errors).length > 0) {
    set(formAtom, { ...form, errors })
    return
  }

  await api.post('/users', { name: form.name, email: form.email })
  set(formAtom, { name: '', email: '', errors: {} })
})

Atomization — дробление объектов

Jotai позволяет разделить один объект на независимо обновляемые атомы:

import { splitAtom } from 'jotai/utils'

const todosAtom = atom<Todo[]>([
  { id: 1, text: 'Купить молоко', done: false },
  { id: 2, text: 'Написать тесты', done: true },
])

export const todoAtomsAtom = splitAtom(todosAtom)
function TodoList() {
  const [todoAtoms] = useAtom(todoAtomsAtom)
  return (
    <ul>
      {todoAtoms.map((todoAtom) => (
        <TodoItem key={`${todoAtom}`} atom={todoAtom} />
      ))}
    </ul>
  )
}

function TodoItem({ atom }: { atom: PrimitiveAtom<Todo> }) {
  const [todo, setTodo] = useAtom(atom)
  // ре-рендерится только при изменении этого конкретного todo
  return (
    <li>
      <input
        type="checkbox"
        checked={todo.done}
        onChange={(e) => setTodo({ ...todo, done: e.target.checked })}
      />
      {todo.text}
    </li>
  )
}

Персистентность

import { atomWithStorage } from 'jotai/utils'

export const themeAtom = atomWithStorage<'light' | 'dark'>('theme', 'light')
export const tokenAtom = atomWithStorage<string | null>('auth_token', null)

atomWithStorage работает с localStorage, sessionStorage или любым кастомным хранилищем.

Provider и изолированные scope

По умолчанию атомы — глобальные синглтоны. Для изоляции (тесты, storybook, несколько независимых экземпляров):

import { createStore, Provider } from 'jotai'

const storeA = createStore()
const storeB = createStore()

storeA.set(countAtom, 10)
storeB.set(countAtom, 20)

function App() {
  return (
    <>
      <Provider store={storeA}><Counter /></Provider>
      <Provider store={storeB}><Counter /></Provider>
    </>
  )
}

DevTools

import { DevTools } from 'jotai-devtools'
import 'jotai-devtools/styles.css'

function App() {
  return (
    <>
      {process.env.NODE_ENV === 'development' && <DevTools />}
      <Router />
    </>
  )
}

Отображает дерево атомов, текущие значения, историю изменений.

Структура файлов

src/
  atoms/
    auth.atom.ts
    ui.atom.ts        # тема, язык, sidebar open/close
    cart.atom.ts
    derived/          # производные и async атомы
      user.atom.ts
      products.atom.ts

Что делаем

Аудит текущего состояния в приложении, перевод на атомарную модель, настройка async атомов с Suspense там, где нужна стриминговая загрузка, подключение devtools, написание тестов атомов через createStore().

Срок: 1–2 дня.