К основному содержанию
скилл CX Codex

compound-pattern — скилл для Claude Code: составные компоненты

Учит агента собирать составные компоненты с общим неявным состоянием: табы, аккордеоны, выпадающие меню. Пригодится, когда подкомпоненты должны согласовывать поведение без прокидывания пропсов.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add PatternsDev/skills --skill compound-pattern -a claude-code
Открыть на GitHub

Что делает compound-pattern

Скилл описывает паттерн составных компонентов на React. Родитель держит состояние и раздаёт его детям через Context API, а дочерние части крепятся статическими свойствами к родителю: FlyOut.Toggle, FlyOut.List, FlyOut.Item. В примере разбирается FlyOut с кнопкой-переключателем и списком пунктов Edit и Delete.

Внутри есть раздел «When to Use» и «When NOT to Use», инструкции и два подхода: Context API и React.Children.map с клонированием детей. Второй способ передаёт open и toggle прямо в клонированные элементы, без контекста. Автор советует предпочесть Context — он гибче при вложенности. Ещё советует мемоизировать значения контекста, иначе в сложных сценах пойдут лишние ререндеры.

Ограничения прописаны честно: если подкомпоненты не делят осмысленное состояние, Context добавляет лишний оверхед. Для простых одноразовых интерфейсов один компонент с пропсами понятнее. И бывает, что неявное состояние делает поведение непредсказуемым для того, кто компонент использует. Скилл подхватывается на файлах **/*.tsx и **/*.jsx.

Какие задачи решает

  • собрать табы, где панели знают об активной вкладке без лишних пропсов
  • сделать выпадающее меню с кнопкой и списком пунктов
  • отдать наружу чистый API компонента, спрятав управление состоянием
  • выбрать между Context API и React.Children.map для вложенных детей
  • убрать лишние ререндеры в составном компоненте через мемоизацию контекста

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

  • Собери компонент табов по паттерну compound-pattern: родитель держит состояние, панели берут его из контекста
  • Переделай это выпадающее меню на составной компонент с Toggle и List как статическими свойствами
  • Проверь мой аккордеон: не нужен ли здесь Context вместо прокидывания пропсов

Как установить compound-pattern

1Через Skills CLI (Vercel)

npx skills add PatternsDev/skills --skill compound-pattern -a claude-code

2Вручную: положить файлы

~/.claude/skills/compound-pattern/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/compound-pattern/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Из того же набора44

hooks-pattern Скилл объясняет React Hooks и помогает вынести повторяющуюся логику состояния в кастомные хуки. Пригодится, когда в компонентах копится дублирование. CX Codex +1 1 100установок render-props-pattern Скилл объясняет паттерн render props в React и подсказывает, когда вместо него взять хуки. Пригодится, если логику рендера нужно разделить между компонентами. CX Codex +1 1 000установок client-side-rendering Скилл объясняет клиентский рендеринг в React: когда он уместен, а когда страница провалится по SEO и первой отрисовке. CX Codex +1 1 000установок hoc-pattern Обучает ИИ паттерну Higher-Order Component для переиспользования логики в React. Пригодится при выносе авторизации, логов или загрузки данных. CX Codex +1 1 000установок react-server-components Скилл объясняет React Server Components: какие компоненты рендерить на сервере и где ставить 'use client'. Пригодится в Next.js App Router. CX Codex +1 954установок progressive-hydration Скилл объясняет агенту, как разбить гидратацию серверного React-приложения на части и отложить JavaScript для неважных блоков. CX Codex +1 952установок react-selective-hydration Скилл учит агента настраивать выборочную гидратацию и потоковый SSR в React 18+. Пригодится, когда тяжёлые компоненты с запросами данных тормозят интерактивность страницы. CX Codex +1 943установок server-side-rendering Скилл объясняет серверный рендеринг в React и подсказывает, когда он оправдан, а когда хватит статики или клиентского рендеринга. CX Codex +1 942установок

Похожие

react-component-architecture aj-geddes Скилл задаёт агенту правила проектирования React-компонентов: функциональные компоненты, хуки, композиция и TypeScript. Пригодится, когда растёт библиотека переиспользуемых компонентов. CX Codex +1 674установок react-development manutej Скилл для ИИ-агента: подсказывает, как писать компоненты, хуки, состояние и эффекты в React по официальной документации. CX Codex +1 464установок nextjs-client-cookie-pattern wsimmonds Скилл показывает, как из клиентского компонента Next.js выставить серверную куку через server action. Пригодится для тем, авторизации и языковых настроек. CX Codex +1 323установок frontend-designer jamesrochabrun Скилл для сборки доступных и адаптивных компонентов интерфейса: дизайн-токены, современный CSS, паттерны React, Vue и Svelte. CX Codex +1 335установок shadcn-ui jezweb Ставит и настраивает компоненты shadcn/ui в React-проекте: подбирает их по задаче, следит за порядком установки и зависимостями. CX Codex +1 3 600установок tanstack-store tanstack-skills Скилл по TanStack Store: реактивный стор без привязки к фреймворку, адаптеры для React, Vue, Solid, Angular и Svelte. Пригодится, когда агент пишет состояние на @tanstack/store. CX Codex +1 1 800установок

Рядом в теме «Фронтенд и UI»

Всё по теме «Фронтенд и UI» →

Вопросы о compound-pattern

Как установить compound-pattern в Claude?

Выполни npx skills add PatternsDev/skills --skill compound-pattern -a claude-code. Скилл встанет в ~/.claude/skills/compound-pattern/SKILL.md.

Что делает compound-pattern?

Объясняет паттерн составных компонентов: родитель хранит общее состояние и отдаёт его детям через Context, а части крепятся к нему статическими свойствами. Агент применяет это при сборке табов, меню и аккордеонов.

Когда этот паттерн лучше не использовать?

Если подкомпоненты не делят осмысленное состояние, Context даёт лишний оверхед. Для простых одноразовых интерфейсов один компонент с пропсами яснее.

Исходный текст

SKILL.md · MIT
---
name: compound-pattern
description: Teaches the compound component pattern for shared implicit state. Use when building related components like tabs, accordions, or dropdowns that need to coordinate without explicit prop passing.
paths:
  - "**/*.tsx"
  - "**/*.jsx"
license: MIT
metadata:
  author: patterns.dev
  version: "1.1"
related_skills:
  - "hooks-pattern"
  - "hoc-pattern"
---

# Compound Pattern

## Table of Contents

- [When to Use](#when-to-use)
- [When NOT to Use](#when-not-to-use)
- [Instructions](#instructions)
- [Details](#details)
- [Source](#source)

In our application, we often have components that belong to each other. They're dependent on each other through the shared state, and share logic together. You often see this with components like `select`, dropdown components, or menu items. The **compound component pattern** allows you to create components that all work together to perform a task.

## When to Use

- Use this when building components like dropdowns, tabs, or menus with related sub-components
- This is helpful when you want to provide a clean component API without exposing internal state management

## When NOT to Use

- When the sub-components don't share meaningful state — the pattern adds unnecessary Context overhead
- For simple one-off UIs where a single component with props is clearer
- When the implicit state sharing makes the component behavior hard to predict for consumers

## Instructions

- Use React Context API to share state between the parent compound component and its children
- Attach child components as static properties on the parent (e.g., `FlyOut.Toggle`, `FlyOut.List`)
- Memoize context values to avoid unnecessary re-renders in complex scenarios
- Prefer the Context approach over `React.Children.map` for more flexible component nesting

## Details

### Context API

Let's look at an example: we have a list of squirrel images! Besides just showing squirrel images, we want to add a button that makes it possible for the user to edit or delete the image. We can implement a `FlyOut` component that shows a list when the user toggles the component.

Within a `FlyOut` component, we essentially have three things:

- The `FlyOut` wrapper, which contains the toggle button and the list
- The `Toggle` button, which toggles the `List`
- The `List`, which contains the list of menu items

Using the Compound component pattern with React's [Context API](https://reactjs.org/docs/context.html) is perfect for this example!

First, let's create the `FlyOut` component. This component keeps the state, and returns a `FlyOutProvider` with the value of the toggle to all the children it receives.

```js
const FlyOutContext = createContext();

function FlyOut(props) {
  const [open, toggle] = useState(false);

  return (
    <FlyOutContext.Provider value={{ open, toggle }}>
      {props.children}
    </FlyOutContext.Provider>
  );
}
```

We now have a stateful `FlyOut` component that can pass the value of `open` and `toggle` to its children!

Let's create the `Toggle` component. This component simply renders the component on which the user can click in order to toggle the menu.

```js
function Toggle() {
  const { open, toggle } = useContext(FlyOutContext);

  return (
    <div onClick={() => toggle(!open)}>
      <Icon />
    </div>
  );
}
```

In order to actually give `Toggle` access to the `FlyOutContext` provider, we need to render it as a child component of `FlyOut`! We can also make the `Toggle` component a property of the `FlyOut` component!

```js
const FlyOutContext = createContext();

function FlyOut(props) {
  const [open, toggle] = useState(false);

  return (
    <FlyOutContext.Provider value={{ open, toggle }}>
      {props.children}
    </FlyOutContext.Provider>
  );
}

function Toggle() {
  const { open, toggle } = useContext(FlyOutContext);

  return (
    <div onClick={() => toggle(!open)}>
      <Icon />
    </div>
  );
}

FlyOut.Toggle = Toggle;
```

This means that if we ever want to use the `FlyOut` component in any file, we only have to import `FlyOut`!

```js
import React from "react";
import { FlyOut } from "./FlyOut";

export default function FlyoutMenu() {
  return (
    <FlyOut>
      <FlyOut.Toggle />
    </FlyOut>
  );
}
```

Just a toggle is not enough. We also need to have a `List` with list items, which open and close based on the value of `open`.

```js
function List({ children }) {
  const { open } = React.useContext(FlyOutContext);
  return open && <ul>{children}</ul>;
}

function Item({ children }) {
  return <li>{children}</li>;
}
```

The `List` component renders its children based on whether the value of `open` is `true` or `false`. Let's make `List` and `Item` a property of the `FlyOut` component, just like we did with the `Toggle` component.

```js
const FlyOutContext = createContext();

function FlyOut(props) {
  const [open, toggle] = useState(false);

  return (
    <FlyOutContext.Provider value={{ open, toggle }}>
      {props.children}
    </FlyOutContext.Provider>
  );
}

function Toggle() {
  const { open, toggle } = useContext(FlyOutContext);

  return (
    <div onClick={() => toggle(!open)}>
      <Icon />
    </div>
  );
}

function List({ children }) {
  const { open } = useContext(FlyOutContext);
  return open && <ul>{children}</ul>;
}

function Item({ children }) {
  return <li>{children}</li>;
}

FlyOut.Toggle = Toggle;
FlyOut.List = List;
FlyOut.Item = Item;
```

We can now use them as properties on the `FlyOut` component! In this case, we want to show two options to the user: **Edit** and **Delete**. Let's create a `FlyOut.List` that renders two `FlyOut.Item` components, one for the **Edit** option, and one for the **Delete** option.

```js
import React from "react";
import { FlyOut } from "./FlyOut";

export default function FlyoutMenu() {
  return (
    <FlyOut>
      <FlyOut.Toggle />
      <FlyOut.List>
        <FlyOut.Item>Edit</FlyOut.Item>
        <FlyOut.Item>Delete</FlyOut.Item>
      </FlyOut.List>
    </FlyOut>
  );
}
```

Perfect! We just created an entire `FlyOut` component without adding any state in the `FlyOutMenu` itself!

The compound pattern is great when you're building a component library. You'll often see this pattern when using UI libraries like [Semantic UI](https://react.semantic-ui.com/modules/dropdown/#types-dropdown).

### `React.Children.map`

We can also implement the Compound Component pattern by mapping over the children of the component. We can add the `open` and `toggle` properties to these elements, by cloning them with the additional props.

```js
export function FlyOut(props) {
  const [open, toggle] = React.useState(false);

  return (
    <div>
      {React.Children.map(props.children, (child) =>
        React.cloneElement(child, { open, toggle })
      )}
    </div>
  );
}
```

All children components are cloned, and passed the value of `open` and `toggle`. Instead of having to use the Context API like in the previous example, we now have access to these two values through `props`.

### Pros

Compound components manage their own internal state, which they share among the several child components. When implementing a compound component, we don't have to worry about managing the state ourselves.

When importing a compound component, we don't have to explicitly import the child components that are available on that component.

```js
import { FlyOut } from "./FlyOut";

export default function FlyoutMenu() {
  return (
    <FlyOut>
      <FlyOut.Toggle />
      <FlyOut.List>
        <FlyOut.Item>Edit</FlyOut.Item>
        <FlyOut.Item>Delete</FlyOut.Item>
      </FlyOut.List>
    </FlyOut>
  );
}
```


### Cons

When using the `React.Children.map` to provide the values, the component nesting is limited. Only _direct children_ of the parent component will have access to the `open` and `toggle` props, meaning we can't wrap any of these components in another component.

```js
export default function FlyoutMenu() {
  return (
    <FlyOut>
      {/* This breaks */}
      <div>
        <FlyOut.Toggle />
        <FlyOut.List>
          <FlyOut.Item>Edit</FlyOut.Item>
          <FlyOut.Item>Delete</FlyOut.Item>
        </FlyOut.List>
      </div>
    </FlyOut>
  );
}
```

Cloning an element with `React.cloneElement` performs a shallow merge. Already existing props will be merged together with the new props that we pass. This could end up in a naming collision, if an already existing prop has the same name as the props we're passing to the `React.cloneElement` method. As the props are shallowly merged, the value of that prop will be overwritten with the latest value that we pass.

## Source

- [patterns.dev/react/compound-pattern](https://patterns.dev/react/compound-pattern)

### References

- [Render Props - React](https://reactjs.org/docs/render-props.htm)
- [React Hooks: Compound Components - Kent C. Dodds](https://kentcdodds.com/blog/compound-components-with-react-hooks)

© patterns.dev · MIT · PatternsDev/skills

Смотрите также

Спонсоры проекта

Компании, которые помогают держать аналитику открытой