1(this["webpackJsonpadvanced-react-patterns"]=this["webpackJsonpadvanced-react-patterns"]||[]).push([[27],{182:function(e,t,n){"use strict";n.r(t),n.d(t,"readingTime",(function(){return p})),n.d(t,"default",(function(){return r})),n.d(t,"tableOfContents",(function(){return c})),n.d(t,"frontMatter",(function(){return i}));var a=n(42),o=(n(1),n(208)),s=["components"],p={text:"4 min read",minutes:3.305,time:198300,words:661},l={};function r(e){var t=e.components,n=Object(a.a)(e,s);return Object(o.a)("wrapper",Object.assign({},l,n,{components:t,mdxType:"MDXLayout"}),Object(o.a)("h1",{id:"compound-components"},"Compound Components"),Object(o.a)("h2",{id:"-your-notes"},"\ud83d\udcdd Your Notes"),Object(o.a)("p",null,"Elaborate on your learnings here in ",Object(o.a)("inlineCode",{parentName:"p"},"src/exercise/03.md")),Object(o.a)("h2",{id:"background"},"Background"),Object(o.a)("p",null,Object(o.a)("strong",{parentName:"p"},"One liner:")," The Compound Components Pattern enables you to provide a set of\ncomponents that implicitely share state for a simple yet powerful declarative\nAPI for reusable components."),Object(o.a)("p",null,"Compound components are components that work together to form a complete UI. The\nclassic example of this is ",Object(o.a)("inlineCode",{parentName:"p"},"<select>")," and ",Object(o.a)("inlineCode",{parentName:"p"},"<option>")," in HTML:"),Object(o.a)("pre",null,Object(o.a)("code",{parentName:"pre",className:"language-markup","data-language":"markup","data-highlighted-line-numbers":"",dangerouslySetInnerHTML:{__html:'<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>select</span><span class="token punctuation">></span></span>\n <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>1<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 1<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>option</span><span class="token punctuation">></span></span>\n <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>2<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 2<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>option</span><span class="token punctuation">></span></span>\n<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>select</span><span class="token punctuation">></span></span>\n'}})),Object(o.a)("p",null,"The ",Object(o.a)("inlineCode",{parentName:"p"},"<select>")," is the element responsible for managing the state of the UI, and\nthe ",Object(o.a)("inlineCode",{parentName:"p"},"<option>")," elements are essentially more configuration for how the select\nshould operate (specifically, which options are available and their values)."),Object(o.a)("p",null,"Let\u2019s imagine that we were going to implement this native control manually. A\nnaive implementation would look something like this:"),Object(o.a)("pre",null,Object(o.a)("code",{parentName:"pre",className:"language-tsx","data-language":"tsx","data-highlighted-line-numbers":"",dangerouslySetInnerHTML:{__html:'<span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">CustomSelect</span></span>\n <span class="token attr-name">options</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">[</span>\n <span class="token punctuation">{</span>value<span class="token operator">:</span> <span class="token string">\'1\'</span><span class="token punctuation">,</span> display<span class="token operator">:</span> <span class="token string">\'Option 1\'</span><span class="token punctuation">}</span><span class="token punctuation">,</span>\n <span class="token punctuation">{</span>value<span class="token operator">:</span> <span class="token string">\'2\'</span><span class="token punctuation">,</span> display<span class="token operator">:</span> <span class="token string">\'Option 2\'</span><span class="token punctuation">}</span><span class="token punctuation">,</span>\n <span class="token punctuation">]</span><span class="token punctuation">}</span></span>\n<span class="token punctuation">/></span></span>\n'}})),Object(o.a)("p",null,"This works fine, but it\u2019s less extensible/flexible than a compound components\nAPI. For example. What if I want to supply additional attributes on the\n",Object(o.a)("inlineCode",{parentName:"p"},"<option>")," that\u2019s rendered, or I want the ",Object(o.a)("inlineCode",{parentName:"p"}
1,"display")," to change based on whether\nit\u2019s selected? We can easily add API surface area to support these use cases,\nbut that\u2019s just more for us to code and more for users to learn. That\u2019s where\ncompound components come in really handy!"),Object(o.a)("p",null,"Shout-out to ",Object(o.a)("a",{parentName:"p",href:"https://twitter.com/ryanflorence"},"Ryan Florence")," for creating this\npattern."),Object(o.a)("p",null,Object(o.a)("strong",{parentName:"p"},"Real World Projects that use this pattern:")),Object(o.a)("ul",null,Object(o.a)("li",{parentName:"ul"},Object(o.a)("a",{parentName:"li",href:"https://reacttraining.com/reach-ui/tabs"},Object(o.a)("inlineCode",{parentName:"a"},"@reach/tabs"))),Object(o.a)("li",{parentName:"ul"},Object(o.a)("a",{parentName:"li",href:"https://reacttraining.com/reach-ui/accordion"},Object(o.a)("inlineCode",{parentName:"a"},"@reach/accordion"))),Object(o.a)("li",{parentName:"ul"},"Actually most of ",Object(o.a)("a",{parentName:"li",href:"https://reacttraining.com/reach-ui"},"Reach UI")," implements\nthis pattern")),Object(o.a)("h2",{id:"exercise"},"Exercise"),Object(o.a)("p",null,"Production deploys:"),Object(o.a)("ul",null,Object(o.a)("li",{parentName:"ul"},Object(o.a)("a",{parentName:"li",href:"http://advanced-react-patterns-next.netlify.app/isolated/exercise/03.tsx"},"Exercise")),Object(o.a)("li",{parentName:"ul"},Object(o.a)("a",{parentName:"li",href:"http://advanced-react-patterns-next.netlify.app/isolated/final/03.tsx"},"Final"))),Object(o.a)("p",null,"What we want to do in this exercise is allow users to render something when the\ntoggle button is on and to render something else when that toggle button is off\nwithout them being able to see the state that\u2019s controlling whether it\u2019s shown\nor not."),Object(o.a)("p",null,"Every reusable component starts out as a simple implementation for a specific\nuse case. It\u2019s advisable to not overcomplicate your components and try to solve\nevery conceivable problem that you don\u2019t yet have (and likely will never have).\nBut as changes come (and they almost always do), then you\u2019ll want the\nimplementation of your component to be flexible and changeable. Learning how to\ndo that is the point of much of this workshop."),Object(o.a)("p",null,"This is why we\u2019re starting with a super simple ",Object(o.a)("inlineCode",{parentName:"p"},"<Toggle />")," component."),Object(o.a)("p",null,"In this exercise we\u2019re going to make ",Object(o.a)("inlineCode",{parentName:"p"},"<Toggle />")," the parent of a few compound\ncomponents:"),Object(o.a)("ul",null,Object(o.a)("li",{parentName:"ul"},Object(o.a)("inlineCode",{parentName:"li"},"<ToggleOn />")," renders children when the ",Object(o.a)("inlineCode",{parentName:"li"},"on")," state is ",Object(o.a)("inlineCode",{parentName:"li"},"true")),Object(o.a)("li",{parentName:"ul"},Object(o.a)("inlineCode",{parentName:"li"},"<ToggleOff />")," renders children when the ",Object(o.a)("inlineCode",{parentName:"li"},"on")," state is ",Object(o.a)("inlineCode",{parentName:"li"}
1,"false")),Object(o.a)("li",{parentName:"ul"},Object(o.a)("inlineCode",{parentName:"li"},"<ToggleButton />")," renders the ",Object(o.a)("inlineCode",{parentName:"li"},"<Switch />")," with the ",Object(o.a)("inlineCode",{parentName:"li"},"on")," prop set to the ",Object(o.a)("inlineCode",{parentName:"li"},"on"),"\nstate and the ",Object(o.a)("inlineCode",{parentName:"li"},"onClick")," prop set to ",Object(o.a)("inlineCode",{parentName:"li"},"toggle"),".")),Object(o.a)("p",null,"We have a Toggle component that manages the state, and we want to render\ndifferent parts of the UI however we want. We want control over the presentation\nof the UI."),Object(o.a)("p",null,"\ud83e\udd89 The fundamental challenge you face with an API like this is the state shared\nbetween the components is implicit, meaning that the developer using your\ncomponent cannot actually see or interact with the state (",Object(o.a)("inlineCode",{parentName:"p"},"on"),") or the\nmechanisms for updating that state (",Object(o.a)("inlineCode",{parentName:"p"},"toggle"),") that are being shared between the\ncomponents."),Object(o.a)("p",null,"So in this exercise, we\u2019ll solve that problem by using the \ud83d\udcdc\n",Object(o.a)("a",{parentName:"p",href:"https://reactjs.org/docs/hooks-reference.html#usecontext"},"React Context API"),"!"),Object(o.a)("p",null,"Your job will be to make a ",Object(o.a)("inlineCode",{parentName:"p"},"ToggleContext")," which will be used to implicitly\nshare the state between these components. The ",Object(o.a)("inlineCode",{parentName:"p"},"Toggle")," component will render the\n",Object(o.a)("inlineCode",{parentName:"p"},"ToggleContext.Provider")," and the other compound components will access that\nimplicit state via ",Object(o.a)("inlineCode",{parentName:"p"},"useContext(ToggleContext)"),"."),Object(o.a)("p",null,"\ud83e\uddba TypeScript might not like your ",Object(o.a)("inlineCode",{parentName:"p"},"useContext")," call depending on how you set up\nyour context. We\u2019ll deal with this in the extra credit."),Object(o.a)("h2",{id:"extra-credit"},"Extra Credit"),Object(o.a)("h3",{id:"1--custom-hook-validation"},"1. \ud83d\udcaf custom hook validation"),Object(o.a)("p",null,Object(o.a)("a",{parentName:"p",href:"http://advanced-react-patterns-next.netlify.app/isolated/final/03.extra-1.tsx"},"Production deploy")),Object(o.a)("p",null,"Change the ",Object(o.a)("inlineCode",{parentName:"p"},"App")," function to this:"),Object(o.a)("pre",null,Object(o.a)("code",{parentName:"pre",className:"language-javascript","data-language":"javascript","data-highlighted-line-numbers":"",dangerouslySetInnerHTML:{__html:'<span class="token keyword">const</span> <span class="token function-variable function">App</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator"><</span>ToggleButton <span class="token operator">/</span><span class="token operator">></span>\n'}})),Object(o.a)("p",null,"Why doesn\u2019t that work? Can you figure out a way to give the developer a better\nerror message?"),Object(o.a)("h2",{id:"-feedback"},"\ud83e\udd89 Feedback"),Object(o.a)("p",null,"Fill out\n",Object(o.a)("a",{parentName:"p",href:"https://ws.kcd.im/?ws=Advanced%20React%20Patterns%20%F0%9F%A4%AF&e=03%3A%20Compound%20Components&em="},"the feedback form"),"."))}r.isMDXComponent=!0;var c=function(){return[{id:"-your-notes",level:2,title:"\ud83d\udcdd Your Notes",children:[]},{id:"background",level:2,title:"Background",children:[]},{id:"exercise",level:2,title:"Exercise",children:[]},{id:"extra-credit",level:2,title:"Extra Credit",children:[{id:"1--custom-hook-validation",level:3,title:"1. \ud83d\udcaf custom hook validation",children:[]}]},{id:"-feedback",level:2,title:"\ud83e\udd89 Feedback",children:[]}]},i={}},208:function(e,t,n){"use strict";n.d(t,"a",(function(){return d}));var a=n(1),o=n.n(a);function s(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function p(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};
1t%2?p(Object(n),!0).forEach((function(t){s(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):p(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function r(e,t){if(null==e)return{};var n,a,o=function(e,t){if(null==e)return{};var n,a,o={},s=Object.keys(e);for(a=0;a<s.length;a++)n=s[a],t.indexOf(n)>=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(a=0;a<s.length;a++)n=s[a],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var c=o.a.createContext({}),i=function(e){var t=o.a.useContext(c),n=t;return e&&(n="function"===typeof e?e(t):l(l({},t),e)),n},u={inlineCode:"code",wrapper:function(e){var t=e.children;return o.a.createElement(o.a.Fragment,{},t)}},h=o.a.forwardRef((function(e,t){var n=e.components,a=e.mdxType,s=e.originalType,p=e.parentName,c=r(e,["components","mdxType","originalType","parentName"]),h=i(n),d=a,m=h["".concat(p,".").concat(d)]||h[d]||u[d]||s;return n?o.a.createElement(m,l(l({ref:t},c),{},{components:n})):o.a.createElement(m,l({ref:t},c))}));function d(e,t){var n=arguments,a=t&&t.mdxType;if("string"===typeof e||a){var s=n.length,p=new Array(s);p[0]=h;var l={};for(var r in t)hasOwnProperty.call(t,r)&&(l[r]=t[r]);l.originalType=e,l.mdxType="string"===typeof e?e:a,p[1]=l;for(var c=2;c<s;c++)p[c]=n[c];return o.a.createElement.apply(null,p)}return o.a.createElement.apply(null,n)}h.displayName="MDXCreateElement"}}]); 2//# sourceMappingURL=27.043ddcf0.chunk.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.