{"id":4800,"date":"2024-12-16T11:37:38","date_gmt":"2024-12-16T16:37:38","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=4800"},"modified":"2024-12-16T11:37:38","modified_gmt":"2024-12-16T16:37:38","slug":"react-19-and-web-component-examples","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/react-19-and-web-component-examples\/","title":{"rendered":"React 19 and Web Component Examples"},"content":{"rendered":"\n<p>There is lots of news of <a href=\"https:\/\/react.dev\/blog\/2024\/12\/05\/react-19\">React 19 and going stable<\/a> and now supporting Web Components. Or&#8230; &#8220;custom elements&#8221; I should say, as that refers to the HTML expression of them as <code>&lt;dashed-elements><\/code>, which is where the trouble laid. Apparently it was hard for React to know, in JSX, if props should be treated as <a href=\"https:\/\/jakearchibald.com\/2024\/attributes-vs-properties\/\">a property or an attribute<\/a>. So they&#8217;ve just decided this is how it will work:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<ul class=\"wp-block-list\">\n<li><strong>Server Side Rendering<\/strong>: props passed to a custom element will render as attributes if their type is a primitive value like&nbsp;<code>string<\/code>,&nbsp;<code>number<\/code>, or the value is&nbsp;<code>true<\/code>. Props with non-primitive types like&nbsp;<code>object<\/code>,&nbsp;<code>symbol<\/code>,&nbsp;<code>function<\/code>, or value&nbsp;<code>false<\/code>&nbsp;will be omitted.<\/li>\n\n\n\n<li><strong>Client Side Rendering<\/strong>: props that match a property on the Custom Element instance will be assigned as properties, otherwise they will be assigned as attributes.<\/li>\n<\/ul>\n<\/blockquote>\n\n\n\n<p>That&#8217;s enough to pass all the tests at <a href=\"https:\/\/custom-elements-everywhere.com\/\">Custom Elements Everywhere<\/a>, which tracks such things. (And apparently every single framework is now 100% fine. Cool.)<\/p>\n\n\n\n<p>This got me thinking about what this actually means and how I might make use of it. I use both React and Web Components sometimes, but only rarely do I combine them, and the last time I did I had more issues with the Shadow DOM than I did with React doing anything funky.<\/p>\n\n\n\n<p>So here I&#8217;ve made a LitElement and rendered it within a React component:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_yyBJxdp\" src=\"\/\/codepen.io\/anon\/embed\/yyBJxdp?height=527&amp;theme-id=47434&amp;slug-hash=yyBJxdp&amp;default-tab=js,result\" height=\"527\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed yyBJxdp\" title=\"CodePen Embed yyBJxdp\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>What I was thinking there was&#8230; <em>what if I make a <code>&lt;designsystem-button&gt;<\/code> and need a click handler on it? <\/em>Turns out that&#8217;s not really a problem. You can just slap a React <code>onClick<\/code> right on it and it&#8217;s fine.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">MyCard<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>blah blah<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n  <span class=\"hljs-comment\">&lt;!-- this is fine --&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">designsystem-button<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> {}}&gt;<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">designsystem-button<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">MyCard<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That wasn&#8217;t a problem anyway, apparently. <\/p>\n\n\n\n<p>What <em>is<\/em> a problem is if I want to send in a function from React-land for the Web Component to call. You&#8217;d think we could send the function in how LitElement generally wants you to do that like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-comment\">&lt;!-- nope --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">designsystem-button<\/span> <span class=\"hljs-attr\">.mySpecialEvent<\/span>=<span class=\"hljs-string\">${specialEvent}<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>But nope, JSX really doesn&#8217;t like that &#8220;dot syntax&#8221; and won&#8217;t compile. <\/p>\n\n\n\n<p>So you gotta send it in more like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">designsystem-button<\/span> <span class=\"hljs-attr\">onSpecialEvent<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> mySpecialEvent()}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Then in order to &#8220;call&#8221; that event, you &#8220;dispatch&#8221; and event named properly. Like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">this<\/span>.dispatchEvent(<span class=\"hljs-keyword\">new<\/span> CustomEvent(<span class=\"hljs-string\">\"SpecialEvent\"<\/span>, { <span class=\"hljs-attr\">bubbles<\/span>: <span class=\"hljs-literal\">true<\/span> }));<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here&#8217;s that with a &#8220;raw&#8221; Web Component:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_RNboKBL\" src=\"\/\/codepen.io\/anon\/embed\/RNboKBL?height=450&amp;theme-id=47434&amp;slug-hash=RNboKBL&amp;default-tab=js,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed RNboKBL\" title=\"CodePen Embed RNboKBL\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>I took that idea from Jared White&#8217;s article <a href=\"https:\/\/thathtml.blog\/2024\/12\/oh-happy-day-react-finally-speaks-web-component\/\">Oh Happy Day! React Finally Speaks Web Components<\/a>. Where he&#8217;s got some other examples. Another is passing in a &#8220;complex object&#8221; which is one of those things that would have been impossible in React 18 and under apparently, and now we can do:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_GgKNrzx\" src=\"\/\/codepen.io\/anon\/embed\/GgKNrzx?height=450&amp;theme-id=47434&amp;slug-hash=GgKNrzx&amp;default-tab=js,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed GgKNrzx\" title=\"CodePen Embed GgKNrzx\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n","protected":false},"excerpt":{"rendered":"<p>&#8220;&#8230; props that match a property on the Custom Element instance will be assigned as properties, otherwise they will be assigned as attributes.&#8221;<\/p>\n","protected":false},"author":1,"featured_media":4807,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[3,62,36],"class_list":["post-4800","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-javascript","tag-react","tag-web-components"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/12\/react-and-web-components.webp?fit=1200%2C600&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/4800","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=4800"}],"version-history":[{"count":4,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/4800\/revisions"}],"predecessor-version":[{"id":4808,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/4800\/revisions\/4808"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/4807"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=4800"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=4800"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=4800"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}