Composition

One liner: The Composition and Layout Components Pattern helps to avoid the prop drilling problem and enhances the reusability of your components.
๐Ÿฆ‰ If you're unfamiliar with the concept of "Prop Drilling" then please read this blog post before going forward.
Let's skip to the end here. It's surprising what you can accomplish by passing react elements rather than treating components as uncrossable boundaries. We'll have a practical example in our exercise, so let me show you a quick and easy contrived example to explain what we'll be doing here:
function App() {
	const [count, setCount] = useState(0)
	const increment = () => setCount(c => c + 1)
	return <Child count={count} increment={increment} />
}

function Child({ count, increment }: { count: number; increment: () => void }) {
	return (
		<div>
			<strong>
				I am a child and I don't actually use count or increment. My child does
				though so I have to accept those as props and forward them along.
			</strong>
			<GrandChild count={count} onIncrementClick={increment} />
		</div>
	)
}

function GrandChild({
	count,
	onIncrementClick,
}: {
	count: number
	onIncrementClick: () => void
}) {
	return (
		<div>
			<small>I am a grand child and I just pass things off to a button</small>
			<button onClick={onIncrementClick}>{count}</button>
		</div>
	)
}
This prop drilling stuff is one of the reasons so many people have jumped onto state management solutions, whether it be libraries or React context. However, if we restructure things a bit, we'll notice that things get quite a bit easier without losing the flexibility we're hoping for.
function App() {
	const [count, setCount] = useState(0)
	const increment = () => setCount(c => c + 1)
	return (
		<Child
			grandChild={
				<GrandChild
					button={<button onClick={onIncrementClick}>{count}</button>}
				/>
			}
		/>
	)
}

function Child({ grandChild }: { grandChild: React.ReactNode }) {
	return (
		<div>
			<strong>
				I am a child and I don't actually use count or increment. My child does
				though so I have to accept those as props and forward them along.
			</strong>
			{grandChild}
		</div>
	)
}

function GrandChild({ button }: { button: React.ReactNode }) {
	return (
		<div>
			<small>I am a grand child and I just pass things off to a button</small>
			{button}
		</div>
	)
}
Now, clearly you can take this too far (our contrived example above probably does), but the point is that by structuring things a little differently, you can keep the components that don't care about state free of the plumbing needed to make it work. If we decided we needed to manage some more state in the App and that was needed in the button then we could update only the app for that.
This style of composition has helped me reduce the amount of components and files I touch (break?) when I need to make a change and it's also made my abstractions much easier (imagine if we wanted to reuse the Child from above but needed to customize the grandChild. Much easier when we're just accepting a prop for it).
When we structure our components to only really deal with props it actually cares about, then it becomes more of a "layout" component. A component responsible for laying out the react elements it accepts as props. If you're familiar with Vue, this concept is similar to the concept of scoped slots.
๐Ÿ“œ Read more about this in my blog post: One React mistake that's slowing you down
Real World Projects that use this pattern:
  • kentcdodds.com (for the hero component you see at the top of most pages)