react-composition

React composition patterns for scalable component architecture. Use when refactoring components with boolean prop proliferation, building flexible component libraries, designing reusable component APIs, or working with compound components and context providers.

Safety Notice

This listing is from the official public ClawHub registry. Review SKILL.md and referenced scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "react-composition" with this command: npx skills add wpank/react-composition

React Composition Patterns

Composition patterns for building flexible, maintainable React components. Avoid boolean prop proliferation by using compound components, lifting state, and composing internals. These patterns make codebases easier to work with as they scale.

When to Apply

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

Pattern Overview

#PatternImpact
1Avoid Boolean PropsCRITICAL
2Compound ComponentsHIGH
3Context Interface (DI)HIGH
4State LiftingHIGH
5Explicit VariantsMEDIUM
6Children Over Render PropsMEDIUM

Installation

OpenClaw / Moltbot / Clawbot

npx clawhub@latest install react-composition

1. Avoid Boolean Prop Proliferation

Don't add boolean props like isThread, isEditing, isDMThread to customize behavior. Each boolean doubles possible states and creates unmaintainable conditional logic. Use composition instead.

// BAD — boolean props create exponential complexity
function Composer({ isThread, isDMThread, isEditing, isForwarding }: Props) {
  return (
    <form>
      <Input />
      {isDMThread ? <AlsoSendToDMField /> : isThread ? <AlsoSendToChannelField /> : null}
      {isEditing ? <EditActions /> : isForwarding ? <ForwardActions /> : <DefaultActions />}
    </form>
  )
}

// GOOD — composition eliminates conditionals
function ChannelComposer() {
  return (
    <Composer.Frame>
      <Composer.Input />
      <Composer.Footer><Composer.Attachments /><Composer.Submit /></Composer.Footer>
    </Composer.Frame>
  )
}

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

Each variant is explicit about what it renders. Shared internals without a monolithic parent.

2. Compound Components

Structure complex components with shared context. Each subcomponent accesses state via context, not props. Export as a namespace object.

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 }, meta: { inputRef } } = use(ComposerContext)
  return <TextInput ref={inputRef} value={state.input}
    onChangeText={(t) => update((s) => ({ ...s, input: t }))} />
}

const Composer = {
  Provider: ComposerProvider, Frame: ComposerFrame,
  Input: ComposerInput, Submit: ComposerSubmit, Footer: ComposerFooter,
}

// Consumers compose exactly what they need
<Composer.Provider state={state} actions={actions} meta={meta}>
  <Composer.Frame>
    <Composer.Input />
    <Composer.Footer><Composer.Formatting /><Composer.Submit /></Composer.Footer>
  </Composer.Frame>
</Composer.Provider>

3. Generic Context Interface (Dependency Injection)

Define a generic interface with state, actions, and meta. Any provider implements this contract — enabling the same UI to work with different state implementations. The provider is the only place that knows how state is managed.

interface ComposerContextValue {
  state: { input: string; attachments: Attachment[]; isSubmitting: boolean }
  actions: { update: (fn: (s: ComposerState) => ComposerState) => void; submit: () => void }
  meta: { inputRef: React.RefObject<TextInput> }
}

// Provider A: Local state for ephemeral forms
function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
  const [state, setState] = useState(initialState)
  return (
    <ComposerContext value={{ state, actions: { update: setState, submit: useForwardMessage() },
      meta: { inputRef: useRef(null) } }}>{children}</ComposerContext>
  )
}

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

Swap the provider, keep the UI. Same Composer.Input works with both.

4. Lift State into Providers

Move state into dedicated provider components so sibling components outside the main UI can access and modify state without prop drilling or refs.

// BAD — state trapped inside component; siblings can't access it
function ForwardMessageComposer() {
  const [state, setState] = useState(initialState)
  return <Composer.Frame><Composer.Input /><Composer.Footer /></Composer.Frame>
}
function ForwardMessageDialog() {
  return (
    <Dialog>
      <ForwardMessageComposer />
      <MessagePreview />        {/* Can't access composer state */}
      <ForwardButton />         {/* Can't call submit */}
    </Dialog>
  )
}

// GOOD — state lifted to provider; any descendant can access it
function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
  const [state, setState] = useState(initialState)
  const submit = useForwardMessage()
  return (
    <Composer.Provider state={state} actions={{ update: setState, submit }}
      meta={{ inputRef: useRef(null) }}>{children}</Composer.Provider>
  )
}
function ForwardMessageDialog() {
  return (
    <ForwardMessageProvider>
      <Dialog>
        <ForwardMessageComposer />
        <MessagePreview />       {/* Reads state from context */}
        <ForwardButton />        {/* Calls submit from context */}
      </Dialog>
    </ForwardMessageProvider>
  )
}
function ForwardButton() {
  const { actions } = use(Composer.Context)
  return <Button onPress={actions.submit}>Forward</Button>
}

Key insight: Components that need shared state don't have to be visually nested — they just need to be within the same provider.

5. Explicit Variant Components

Instead of one component with many boolean props, create explicit variants. Each composes the pieces it needs — self-documenting, no impossible states.

// BAD — what does this render?
<Composer isThread isEditing={false} channelId="abc" showAttachments showFormatting={false} />

// GOOD — immediately clear
<ThreadComposer channelId="abc" />
<EditMessageComposer messageId="xyz" />
<ForwardMessageComposer messageId="123" />

Each variant is explicit about its provider/state, UI elements, and actions.

6. Children Over Render Props

Use children for composition instead of renderX props. Children are more readable and compose naturally.

// BAD — render props
<Composer
  renderHeader={() => <CustomHeader />}
  renderFooter={() => <><Formatting /><Emojis /></>}
/>

// GOOD — children composition
<Composer.Frame>
  <CustomHeader />
  <Composer.Input />
  <Composer.Footer><Composer.Formatting /><SubmitButton /></Composer.Footer>
</Composer.Frame>

When render props are appropriate: When the parent needs to pass data back (e.g., renderItem={({ item, index }) => ...}).

Decision Guide

  1. Component has 3+ boolean props? → Extract explicit variants (1, 5)
  2. Component has render props? → Convert to compound components (2, 6)
  3. Siblings need shared state? → Lift state to provider (4)
  4. Same UI, different data sources? → Generic context interface (3)
  5. Building a component library? → Apply all patterns together

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

Charging Ledger

充电记录账本 - 从截图提取充电信息并记录,支持按周、月查询汇总。**快速暗号**: 充电记录、充电账本、充电汇总。**自然触发**: 记录充电、查询充电费用、充电统计。

Registry SourceRecently Updated
General

qg-skill-sync

从团队 Git 仓库同步最新技能到本机 OpenClaw。支持首次设置、定时自动更新、手动同步和卸载。当用户需要同步技能、设置技能同步、安装或更新团队技能,或提到「技能同步」「同步技能」时使用。

Registry SourceRecently Updated
General

Ad Manager

广告投放管理 - 自动管理广告投放、优化ROI、生成报告。适合:营销人员、电商运营。

Registry SourceRecently Updated