agentleFS
excattexcatt/superclaude-plusplus/skills/composition-patterns/SKILL.md

composition-patterns

Skill14 starsChanged 37 days agoMITView on GitHub

React composition patterns — refactor boolean-prop-heavy components into compound components, flexible APIs, and Context providers. Use for component architecture design and review.

7 other projects use a version of this, 5 of them edited.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

4 rules. 0 are common, 3 are distinctive, and 1 is very specific to this project.

  • 0

    Common

    Most of the 7 edited versions we read keep these.

  • 3

    Distinctive

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Avoid boolean prop abuse, use compound components, state lifting, and composing internals.

    line 14
  2. Distinctive:

    Do NOT use for: Performance optimization (→ react-best-practices), UI/UX inspection (→ web-design-guidelines)

    line 28
  3. Distinctive:

    Create explicit variant components instead of boolean props.

    line 273
  4. Very specific:

    Use children for composition instead of renderX props.

    line 289

“Common” is measured: kept by at least 6 in 10 of the 7 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

React Composition Patterns

Composition patterns for flexible and maintainable React components. Avoid boolean prop abuse, use compound components, state lifting, and composing internals.

When to Use

Use proactively when:

  • Refactoring components with many boolean props
  • Building reusable component libraries
  • Designing flexible component APIs
  • Reviewing component architecture
  • Working with compound components or Context providers

Triggers: composition, compound component, boolean props, component structure, architecture review, provider pattern, context, refactor component

Do NOT use for: Performance optimization (→ react-best-practices), UI/UX inspection (→ web-design-guidelines)

Rule Categories (by Priority)

PriorityCategoryImpactDescription
1Component ArchitectureHIGHComponent structuring
2State ManagementMEDIUMState management patterns
3Implementation PatternsMEDIUMImplementation patterns
4React 19 APIsMEDIUMReact 19 changes

1. Component Architecture (HIGH)

1.1 Prevent Boolean Prop Abuse

Impact: CRITICAL

Boolean props lead to combinatorial explosion. Use composition instead.

// ❌ Boolean prop explosion - unmaintainable
function Composer({
  isThread,
  isDMThread,
  isEditing,
  isForwarding,
}: Props) {
  return (
    <form>
      {isDMThread ? <DMField /> : isThread ? <ThreadField /> : null}
      {isEditing ? <EditActions /> : isForwarding ? <ForwardActions /> : <DefaultActions />}
    </form>
  )
}

// ✅ Composition - explicit variants
function ThreadComposer({ channelId }: { channelId: string }) {
  return (
    <Composer.Frame>
      <Composer.Input />
      <AlsoSendToChannelField id={channelId} />
      <Composer.Footer>
        <Composer.Submit />
      </Composer.Footer>
    </Composer.Frame>
  )
}

function EditComposer() {
  return (
    <Composer.Frame>
      <Composer.Input />
      <Composer.Footer>
        <Composer.CancelEdit />
        <Composer.SaveEdit />
      </Composer.Footer>
    </Composer.Frame>
  )
}

1.2 Use Compound Components

Impact: HIGH

Structure complex components into subcomponents connected by shared context.

// ❌ Monolithic + render props
function Composer({
  renderHeader,
  renderFooter,
  showAttachments,
}: Props) {
  return (
    <form>
      {renderHeader?.()}
      <Input />
      {showAttachments && <Attachments />}
      {renderFooter?.()}
    </form>
  )
}

// ✅ Compound components + shared context
const ComposerContext = createContext<ComposerContextValue | null>(null)

function ComposerProvider({ children, state, actions, meta }: ProviderProps) {
  return (
    <ComposerContext value={{ state, actions, meta }}>
      {children}
    </ComposerContext>
  )
}

function ComposerInput() {
  const { state, actions: { update } } = use(ComposerContext)
  return <TextInput value={state.input} onChangeText={text => update(s => ({ ...s, input: text }))} />
}

// Export as compound component
const Composer = {
  Provider: ComposerProvider,
  Frame: ComposerFrame,
  Input: ComposerInput,
  Submit: ComposerSubmit,
  Footer: ComposerFooter,
}

// Usage
<Composer.Provider state={state} actions={actions} meta={meta}>
  <Composer.Frame>
    <Composer.Input />
    <Composer.Footer>
      <Composer.Submit />
    </Composer.Footer>
  </Composer.Frame>
</Composer.Provider>

2. State Management (MEDIUM)

2.1 Separate State Implementation from UI

Impact: MEDIUM

Provider knows state implementation, UI only uses context interface.

// ❌ UI coupled to state implementation
function ChannelComposer({ channelId }: { channelId: string }) {
  const state = useGlobalChannelState(channelId)  // Coupled to specific implementation
  const { submit } = useChannelSync(channelId)
  return <Composer.Input value={state.input} />
}

// ✅ State management separated in Provider
function ChannelProvider({ channelId, children }: Props) {
  const { state, update, submit } = useGlobalChannel(channelId)
  return (
    <Composer.Provider state={state} actions={{ update, submit }}>
      {children}
    </Composer.Provider>
  )
}

// UI only needs interface
function ChannelComposer() {
  return (
    <Composer.Frame>
      <Composer.Input />  {/* Reads state from context */}
      <Composer.Submit />
    </Composer.Frame>
  )
}

2.2 Define Generic Context Interface

Impact: HIGH

Define generic interface with 3 parts: state, actions, meta.

// Generic interface - any provider can implement
interface ComposerContextValue {
  state: {
    input: string
    attachments: Attachment[]
    isSubmitting: boolean
  }
  actions: {
    update: (updater: (state: State) => State) => void
    submit: () => void
  }
  meta: {
    inputRef: React.RefObject<TextInput>
  }
}

// Provider A: Local state
function ForwardMessageProvider({ children }) {
  const [state, setState] = useState(initialState)
  return <ComposerContext value={{ state, actions: { update: setState, submit }, meta }}>{children}</ComposerContext>
}

// Provider B: Global synced state
function ChannelProvider({ channelId, children }) {
  const { state, update, submit } = useGlobalChannel(channelId)
  return <ComposerContext value={{ state, actions: { update, submit }, meta }}>{children}</ComposerContext>
}

// Same UI works with both providers!

2.3 Lift State to Provider

Impact: HIGH

Lift state to Provider so sibling components can access it.

// ❌ State trapped inside component
function ForwardMessageDialog() {
  return (
    <Dialog>
      <ForwardMessageComposer />  {/* state trapped here */}
      <MessagePreview />          {/* Cannot access state! */}
      <ForwardButton />           {/* Cannot call submit! */}
    </Dialog>
  )
}

// ✅ Lift state to Provider
function ForwardMessageDialog() {
  return (
    <ForwardMessageProvider>
      <Dialog>
        <ForwardMessageComposer />
        <MessagePreview />    {/* Access state via context */}
        <ForwardButton />     {/* Call submit via context */}
      </Dialog>
    </ForwardMessageProvider>
  )
}

// Anywhere inside Provider can access state/actions
function ForwardButton() {
  const { actions } = use(ComposerContext)
  return <Button onPress={actions.submit}>Forward</Button>
}

3. Implementation Patterns (MEDIUM)

3.1 Create Explicit Component Variants

Impact: MEDIUM

Create explicit variant components instead of boolean props.

// ❌ Unclear what UI renders
<Composer isThread isEditing={false} channelId="abc" showAttachments />

// ✅ Immediately clear
<ThreadComposer channelId="abc" />
<EditMessageComposer messageId="xyz" />
<ForwardMessageComposer messageId="123" />

3.2 Prefer Children over Render Props

Impact: MEDIUM

Use children for composition instead of renderX props.

// ❌ Render props - hard to read
<Composer
  renderHeader={() => <CustomHeader />}
  renderFooter={() => <><Formatting /><Emojis /></>}
/>

// ✅ Children - natural composition
<Composer.Frame>
  <CustomHeader />
  <Composer.Input />
  <Composer.Footer>
    <Composer.Formatting />
    <Composer.Emojis />
  </Composer.Footer>
</Composer.Frame>

When render props appropriate: Parent needs to pass data to children

<List data={items} renderItem={({ item, index }) => <Item item={item} />} />

4. React 19 APIs (MEDIUM)

⚠️ React 19+ only. Skip this section for React 18 or below.

4.1 React 19 API Changes

// ❌ forwardRef (unnecessary in React 19)
const ComposerInput = forwardRef<TextInput, Props>((props, ref) => {
  return <TextInput ref={ref} {...props} />
})

// ✅ ref as regular prop
function ComposerInput({ ref, ...props }: Props & { ref?: React.Ref<TextInput> }) {
  return <TextInput ref={ref} {...props} />
}

// ❌ useContext (React 19)
const value = useContext(MyContext)

// ✅ use() - conditional calls possible
const value = use(MyContext)

Quick Checklist

CheckRule
[ ]3+ Boolean props? → Refactor to composition
[ ]Complex conditional rendering? → Create explicit variants
[ ]State trapped in component? → Lift to Provider
[ ]renderX props? → Change to children
[ ]React 19? → Remove forwardRef, use use()

Related Skills

  • /react-best-practices - Performance optimization (waterfall, bundle, rendering)
  • /web-design-guidelines - UI/UX quality (accessibility, interaction)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.