Skip to content

Клиентские компоненты

В клиентских компонентах данные получаются через готовые хуки, которые экспортируются из модуля API. SWR инкапсулирован в хуке — компонент не знает про useSWR, ключи и fetcher.

Создание клиента и хуков — Автоматическая / Ручная генерация.

Правила

  • Только готовые хуки. В компоненте — usePostDetail(slug), не useSWR(['post', slug], () => api.posts.get(slug)).
  • useSWR пишется один раз — в hooks/ модуля API. В клиентских компонентах никогда напрямую.
  • Прямой вызов методов клиента в useEffect запрещён. Это потеря кеша, повторные запросы и гонки.
  • Мутации — через useSWRMutation, тоже инкапсулированный в хуке. В компоненте вызывается готовый trigger.

Чтение

tsx
'use client'

import { usePostDetail } from 'infrastructure/pet-project-api'

export function PostView({ slug }: { slug: string }) {
  const { data: post, error, isLoading } = usePostDetail(slug)

  if (isLoading) return <Spinner />
  if (error) return <ErrorView error={error} />

  return <article>{post?.title}</article>
}

В компоненте нет useSWR, нет ключей, нет fetcher — только готовый хук.

Параметризованный запрос

Хук сам обрабатывает «нет параметра — нет запроса». В компоненте можно безопасно передавать null:

tsx
'use client'

import { useUserDetail } from 'infrastructure/pet-project-api'

export function UserProfile({ userId }: { userId: string | null }) {
  const { data: user } = useUserDetail(userId)

  if (!userId) return <EmptyState />
  return <UserCard user={user} />
}

Внутри useUserDetail ключ становится null, когда userId не задан, и SWR не делает запрос — это поведение зашито в хук, потребитель об этом не думает.

Мутации

Мутации тоже оборачиваются в хук модуля API:

ts
// src/infrastructure/pet-project-api/hooks/use-create-user.hook.ts
import useSWRMutation from 'swr/mutation'
import { mutate } from 'swr'
import { petProjectApi } from '..'
import type { User, UserCreateInput } from '../types'

/**
 * Создание пользователя с инвалидацией списка.
 */
export const useCreateUser = () => {
  return useSWRMutation<User, Error, [string, string, string], UserCreateInput>(
    ['pet-project-api', 'user', 'create'],
    (_key, { arg }) => petProjectApi.user.create(arg),
    {
      onSuccess: () => mutate(['pet-project-api', 'user', 'list']),
    },
  )
}
tsx
'use client'

import { useCreateUser } from 'infrastructure/pet-project-api'

export function CreateUserForm() {
  const { trigger, isMutating } = useCreateUser()

  return (
    <Form
      onSubmit={(input) => trigger(input)}
      disabled={isMutating}
    />
  )
}

В компоненте — снова только хук. Логика инвалидации кеша зашита внутрь, потребитель её не дублирует.

Передача config из компонента

Каждый хук принимает второй (или третий) параметр config?: SWRConfiguration — он пробрасывается в useSWR. Это даёт потребителю точечно настроить ревалидацию, fallbackData, suspense и т.п.:

tsx
'use client'

import { usePostDetail } from 'infrastructure/pet-project-api'

export function PostView({ slug, initialPost }: Props) {
  const { data: post } = usePostDetail(slug, { fallbackData: initialPost })
  // ...
}

Начальное состояние с сервера

Если данные пришли из серверного компонента (см. Серверные компоненты) — передаются в fallbackData через config хука:

tsx
// page.tsx (server)
import { petProjectApi } from 'infrastructure/pet-project-api'

export default async function Page({ params }: { params: { slug: string } }) {
  const initialPost = await petProjectApi.posts.get(params.slug)
  return <PostView slug={params.slug} initialPost={initialPost} />
}
tsx
// post-view.tsx ('use client')
import { usePostDetail } from 'infrastructure/pet-project-api'

export function PostView({ slug, initialPost }: Props) {
  const { data: post } = usePostDetail(slug, { fallbackData: initialPost })
  return <article>{post?.title}</article>
}

Для массового заполнения кеша на странице с несколькими хуками — используется <SWRConfig fallback> обёртка. Серверный компонент собирает данные и передаёт сериализованную карту ключей в провайдер; все вложенные хуки сразу видят кеш.

Запрет прямых вызовов

tsx
// Плохо — прямой fetch в обход клиента
useEffect(() => {
  fetch('/api/users').then(...)
}, [])

// Плохо — клиент без SWR: нет кеша, нет дедупликации
useEffect(() => {
  petProjectApi.user.list().then(setUsers)
}, [])

// Плохо — useSWR в компоненте: SWR должен быть в хуке модуля
const { data } = useSWR(
  ['pet-project-api', 'user', 'list'],
  () => petProjectApi.user.list(),
)

// Хорошо — готовый хук модуля
const { data } = useUserList()

Если для нужной операции хука ещё нет — он добавляется в hooks/ модуля API, не в компонент.