React Composition Patterns: Beyond Boolean Props

Oct 6, 2025

React Composition Patterns: Beyond Boolean Props

I recently watched Fernando Rojo's excellent talk on React composition patterns, and it crystallized something I've struggled with for years: the gradual descent into prop hell that seems inevitable as React codebases scale.

The Familiar Pattern

You build a form component for creating users. It works perfectly. Then requirements change.

You need a form for updating users. So you add an isUpdate boolean prop. The update flow has some differences—it should populate initial state from existing user data and skip the welcome message. So you add hideWelcome. Then hideTermsAndConditions. Then the mutation shouldn't redirect to onboarding, so you add custom success handlers.

Before long, your component looks like this:

<UserForm 
isUpdate
isThread
isDM
hideDropzone
hideWelcome
hideTerms
onlyEditName
isSlugRequired={false}
onCancel={handleCancel}
onSuccess={handleSuccess}
renderSubmit={customSubmit}
/>

The component itself becomes a labyrinth of conditional logic. Each boolean prop creates branching paths through the code. The implementation is now 500+ lines of nested ternaries, and nobody wants to touch it.

A Real-World Example

Michael uses Slack's message composer as the perfect case study. This single UI component appears in multiple contexts:

  • Channels: Global state synced across all devices
  • Channel threads: Includes "also send to channel" checkbox
  • DM threads: Shows "also send as direct message" option
  • Editing messages: No attachment support, shows cancel/save buttons
  • Forwarding messages: Ephemeral local state, submit button rendered outside the composer box

Each variant has subtle UI differences and fundamentally different state management approaches. Some maintain global state that syncs across your phone and computer. Others are completely ephemeral and discarded when dismissed.

Building this as a single component with boolean props would be a maintenance nightmare.

The Alternative: Compound Components

Instead of configuring a monolithic component with props, we can use composition to build flexible, maintainable component APIs.

Here's the traditional approach:

// The prop hell approach
<Composer
isEditing
hideDropzone
onCancel={handleCancel}
renderSubmit={customSubmitButton}
/>

And here's the compound component approach:

// Using composition
<ComposerProvider>
<Composer.Frame>
<Composer.Header />
<Composer.Input />
<Composer.Footer>
<Composer.TextFormat />
<Composer.Emoji />
<CustomCancelButton />
<CustomSaveButton />
</Composer.Footer>
</Composer.Frame>
</ComposerProvider>

Why This Works

1. Context Over Props

The Provider component shares state and actions through React Context. Child components access exactly what they need without prop drilling. There's no need to thread props through multiple levels of components.

function ComposerInput() {
const { value, update } = useComposerContext();

return (
<input
value={value}
onChange={(e) => update(e.target.value)}
/>

);
}

2. JSX Over Conditionals

Want to disable attachment support? Simply don't render <Composer.DropZone />. There's no need for {!hideDropzone && <DropZone />} scattered throughout your component.

Each variant becomes its own distinct component tree:

// Channel composer
<ComposerProvider>
<Composer.DropZone />
<Composer.Frame>
<Composer.Header />
<Composer.Input />
<Composer.Footer>
<Composer.CommonActions />
</Composer.Footer>
</Composer.Frame>
</ComposerProvider>

// Edit message composer (no drop zone, different actions)
<ComposerProvider>
<Composer.Frame>
<Composer.Header />
<Composer.Input />
<Composer.Footer>
<Composer.TextFormat />
<Composer.Emoji />
<CancelButton />
<SaveButton />
</Composer.Footer>
</Composer.Frame>
</ComposerProvider>

3. Lift State When You Need Flexibility

Consider the forward message composer. The submit button appears outside the composer box, in a modal footer. How do you give it access to the composer's state?

The traditional approach might involve lifting state to the parent and passing it back down, or using refs and imperative handlers. Both are awkward.

With compound components, you simply lift the Provider higher:

<ForwardMessageProvider>
<Modal>
<Modal.Body>
<MessagePreview />
<Composer.Frame>
<Composer.Input />
<Composer.Footer>
<Composer.CommonActions />
</Composer.Footer>
</Composer.Frame>
</Modal.Body>

<Modal.Footer>
<CancelButton />
<ForwardButton /> {/* Has access to composer state */}
</Modal.Footer>
</Modal>
</ForwardMessageProvider>

The forward button, despite being outside the composer frame, can access all the composer's state and actions because it's within the Provider's tree.

4. Decouple State Implementation from Interface

Different variants can use completely different state management approaches while maintaining the same interface:

// Channel composer - global state synced across devices
function ChannelComposerProvider({ children }) {
const { value, update, submit } = useGlobalChannelState();

return (
<ComposerContext.Provider value=>
{children}
</ComposerContext.Provider>
);
}

// Forward composer - local ephemeral state
function ForwardComposerProvider({ children }) {
const [value, setValue] = useState('');
const submit = useForwardMessage();

return (
<ComposerContext.Provider value=>
{children}
</ComposerContext.Provider>
);
}

To switch state management for an entire composer, you only need to change the Provider. All children remain unchanged.

The Key Insight

Michael distills this into a simple heuristic:

"If you have a boolean prop that determines which component tree gets rendered from the parent, you've found a good use case for composition."

When you find yourself writing code like this:

{isEditing ? (
<EditFooter />
) : isForwarding ? (
<ForwardFooter />
) : (
<DefaultFooter />
)}

That's a signal to reach for composition instead.

Performance Considerations

You might worry about React Context causing unnecessary re-renders. The React Compiler handles this elegantly. When you use the compiler (which is fantastic and deserves its own article), it automatically memoizes components based on which context values they actually access.

Michael demonstrated this in the React Compiler playground—even though the context object contains multiple values, components only re-render when the specific values they use change.

When to Use Compound Components

This pattern excels when:

  • Multiple related components need to work together
  • Components share state or behavior
  • You need different UI variants of similar functionality
  • You're building a design system or component library
  • You notice yourself adding the fifth boolean prop

When Not to Use Compound Components

Don't reach for this pattern when:

  • You have simple components with only 1-2 levels
  • There's no shared state to manage
  • The added structure doesn't provide clear benefits
  • You'd be over-engineering a straightforward UI

Implications for AI-Assisted Development

Michael raised an interesting point about AI code generation. He's been building with tools like V0 and Cursor using these composition patterns and found that AI produces significantly fewer bugs.

The patterns we choose don't just affect human developers—they affect how well AI can help us build. Well-structured composition appears to give AI clearer constraints and better examples to work from.

This suggests that as AI becomes more integrated into our development workflow, the importance of clean architecture may actually increase rather than diminish.

The Pattern in Practice

Here's the general approach:

  1. Create a Provider that manages state and actions
  2. Build primitive compound components (Frame, Input, Footer, etc.)
  3. Compose them differently for each use case
  4. Create reusable abstractions (like CommonActions) only when truly needed
  5. Lift Providers higher when you need more flexibility

The beauty of this approach is that it scales. Adding a new variant doesn't require modifying a monolithic component—you simply compose the primitives in a new way.

Conclusion

As Michael puts it: "The next time you're 15 booleans deep into your component props, just remember: composition is all you need."

The compound component pattern isn't just about avoiding prop hell. It's about building UIs that remain maintainable as they grow, that give developers clear patterns to follow, and that work well with both human intuition and AI assistance.

If you haven't watched the full talk, I highly recommend it. Michael's examples are illuminating, and the Slack composer case study really drives home how powerful this pattern can be.


Watch the full talk: React Composition Patterns with Compound Components by Fernando Rojo

Recommendations

Understanding CRDTs: The Magic Behind Collaborative Editing

#CRDT

,

#Collaborative Editing

,

#Distributed Systems

,

#Real-time

,

#Tech Explained

A friendly deep dive into CRDTs and how they power real-time collaborative...

Oct 20, 2025

10 Vue Debugging Tips That Will Transform Your Development Workflow

#Vue.js

,

#Debugging

,

#JavaScript

,

#Frontend

,

#DevTools

,

#Development

,

#Web Development

,

#Vue DevTools

Master Vue.js debugging with 10 battle-tested techniques from real developers....

Aug 26, 2025

Building a Cross-Repository Test Automation Pipeline: From Manual QA Nightmares to Automated Excellence

#automation

,

#testing

,

#CI/CD

,

#GitHub Actions

,

#Playwright

,

#SDK

,

#engineering

,

#DevOps

Learn how to build a cross-repository test automation pipeline that reduced our...

Aug 20, 2025

JavaScript Performance Optimization, 10 Techniques That Actually Move the Needle

#javascript

,

#performance

Discover 10 JavaScript performance optimization techniques that deliver real,...

Aug 18, 2025

Building a Blog Publisher MCP Server to Automate Your Content Workflow with Claude

#MCP

,

#Claude

,

#Automation

,

#TypeScript

,

#GitHub

,

#Blogging

,

#Tutorial

,

#AI Tools

Learn how to build a custom MCP server that lets Claude publish and manage blog...

Aug 7, 2025

20 JavaScript Interview Questions You Should Know in 2025

A practical guide to 20 core JavaScript interview questions — with clear...

Jul 24, 2025

Building a Simple, Scalable Feature Flag System

#nextjs

,

#prisma

,

#feature-flags

,

#fullstack

,

#backend

,

#api-routes

,

#clean-architecture

,

#scalable-design

,

#product-rollout

Built a simple yet scalable feature flag system using Next.js API routes and...

Jul 6, 2025

I Refactored Without Changing a Feature — And It Broke Everything

#HyrumsLaw

,

#Refactoring

,

#LegacyCode

,

#CodeSmells

,

#TechDebt

,

#SoftwareEngineering

,

#CleanCode

Understanding Hyrum’s Law with a Real-World Lesson on Porting vs Refactoring

Jul 5, 2025

How to Publish Your First npm Package: Creating Rainbow Highlight with Utilities

#npm

,

#npm-package

,

#web

,

#javascript

Learn how to create and publish your first npm package. This step-by-step guide...

Sep 22, 2024

Google Dorking: Unlocking Hidden Search Capabilities & Insights

#seach

,

#seo

,

#research

Explore 16 advanced Google Dorking techniques to uncover valuable data, security...

Aug 8, 2024

This One HTML Attribute Could Save Your Web App from a Security Nightmare

#web-security

,

#cdn

,

#web

Web security is a critical concern for developers, yet some of the most...

Jun 29, 2024

Are You Still Using Basic CSS? Here Are 7 Tricks to Get Ahead of the Curve

#css

Bored of the same old CSS? Unleash 7 hidden gems to take your designs to the...

Dec 27, 2023

Easiest way to store your logs in a file WITHOUT chaging the source file(node)

#productivity

Often, developers face challenges when dealing with a flood of logs in the...

Dec 21, 2023

Build Your Own Pinterest-Style Masonry Grid: A Step-by-Step Guide

#css

,

#web

,

#layout

Create a masonary grid layout with left to right content flow, supporting...

Dec 10, 2023

Using git diff and git apply to Share Local Changes with Peers

#git

,

#productivity

,

#software_engeneering

,

#dev

git diff and git apply are two powerful Git commands that can be used to share...

Nov 12, 2023

React Portals: Render Components Outside the current DOM Hierarchy

#react

,

#web

The createPortal API in React allows you to render child elements into a...

Jul 27, 2023

Cloning Made Easy: Try degit and Clone Directories within Repos.

#git

,

#productivit

Have you ever faced the dilemma of wanting just a small portion of a repository,...

Jul 19, 2023

Debugging Web Apps with Browser Dev Tools: 6 Amazing Tricks

#browser

,

#debugging

,

#web

Debugging web applications can be a challenging task, with errors like...

Jul 13, 2023

Controlled Versus Uncontrolled Components in React

#react

,

#forms

Understanding State Management Within Forms Comparing controlled and...

Nov 5, 2022

Format Numbers, Dates and Currencies with the Intl Object in Javascript

#javascript

,

#html

,

#web

Intl object can be used to format data into commonly used formats of dates,...

Sep 13, 2022

Image Masking on Hover Using CSS Clip Path and Javascript

#javscript

,

#css

,

#html

Image Masking can be used to add fancy hover highlight effects to images for...

Jul 23, 2022

Recreating CSS Tricks Fancy Grid Hover Effect

#html

,

#css

,

#UI

,

#recreation

CSS Trick had a simple yet cool grid layout which I found dope. So lets try to...

May 21, 2022

File Explorer Recursive React Component

#react

,

#javascript

,

#web

How to create a recursive folder Component using react.

Apr 16, 2022

Add Google Fonts to Your React & NextJS + TailwindCSS Project (Next 14)

#css

,

#tailwindcss

,

#react

,

#nextjs

,

#tailwind

,

#design

Use Google Fonts in Your TailwindCSS Projects

Apr 6, 2022

Event Delegation in Javascript

#javscript

,

#css

,

#html

,

#web

,

#performance

Handling multiple Events in Javascript with minimal CPU Usage

Mar 6, 2022

A Simple Web Accessibility Trick that you most probably missed!

#html

,

#css

,

#web-accessibility

,

#user-experience

Imagine that you cannot use the mouse and have to Navigate a Website with the...

Dec 23, 2021

Top Terminal Commands I Use For Productivity

#linux

,

#cli

,

#terminal

The whole point of development is solving problems. But very often we Developers...

Nov 3, 2021

CSS Logical Properties

#css

,

#html

CSS logical properties are properties which are used to design element on the...

Oct 5, 2021

Fluid Typography in CSS 💧

#css

,

#html

,

#typography

CSS Best Practices in Fluid Typography

Aug 15, 2021

CSS Units in a Nutshell 🐚

#css

,

#html

Are you still writing your css units in pixels and percentages? if you are then...

Aug 8, 2021

Master Markdown in 5minutes ⌚

#markdown

,

#documentation

Markdown is a lightweight markup language for creating formatted text using a...

Aug 1, 2021

What is JAMStack ✨

#jamstack

Jamstack stands for Javascript APIS and Markup and it is based on this idea of...

Jul 31, 2021

+

Check my latest Blog Post

Understanding CRDTs: The Magic Behind Collaborative Editing

Read Now
Oh My Gawwdd!!!!!!!

Wow you have been viewing my site since 20 seconds!

+
+