Mar 10, 2023 - 5 min read
The composition and Layout Components Pattern helps to avoid prop drilling and makes the code more readable and maintainable. The composition pattern is a design pattern that uses the React children prop to compose components together. 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:
1function App() {
2 const [count, setCount] = useState(0);
3 const increment = () => setCount((c) => c + 1);
4 return <Child count={count} increment={increment} />;
5}
6function Child({ count, increment }: { count: number; increment: () => void }) {
7 return (
8 <div>
9 <strong>
10 I am a child and I don't actually use count or increment. My child does
11 though so I have to accept those as props and forward them along.
12 </strong>
13 <GrandChild count={count} onIncrementClick={increment} />
14 </div>
15 );
16}
17function GrandChild({
18 count,
19 onIncrementClick,
20}: {
21 count: number;
22 onIncrementClick: () => void;
23}) {
24 return (
25 <div>
26 <small>I am a grand child and I just pass things off to a button</small>
27 <button onClick={onIncrementClick}>{count}</button>
28 </div>
29 );
30}
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.
1function App() {
2 const [count, setCount] = useState(0);
3 const increment = () => setCount((c) => c + 1);
4 return (
5 <Child
6 grandChild={
7 <GrandChild
8 button={<button onClick={onIncrementClick}>{count}</button>}
9 />
10 }
11 />
12 );
13}
14function Child({ grandChild }: { grandChild: React.ReactNode }) {
15 return (
16 <div>
17 <strong>
18 I am a child and I don't actually use count or increment. My child does
19 though so I have to accept those as props and forward them along.
20 </strong>
21 {grandChild}
22 </div>
23 );
24}
25function GrandChild({ button }: { button: React.ReactNode }) {
26 return (
27 <div>
28 <small>I am a grand child and I just pass things off to a button</small>
29 {button}
30 </div>
31 );
32}