{"id":1224,"date":"2021-01-21T12:09:33","date_gmt":"2021-01-21T20:09:33","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/surface-duo\/?p=1224"},"modified":"2021-01-21T12:09:33","modified_gmt":"2021-01-21T20:09:33","slug":"dual-screen-react-web","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/surface-duo\/dual-screen-react-web\/","title":{"rendered":"Building dual-screen web experiences with React"},"content":{"rendered":"<p>Hello web developers!<\/p>\n<p>\n  Responsive design has always been the cornerstone of web development, rather than designing for a single use case, we focus on creating applications that can adjust to the needs of the platform they are running on. Microsoft Surface Duo is no exception to this and in past blog posts we\u2019ve explored how we can use the <a href=\"https:\/\/devblogs.microsoft.com\/surface-duo\/dual-screen-web-experiences-preview\/?WT.mc_id=blog-surfaceduoblog-aapowell\">CSS and JavaScript primitives<\/a> to create experiences <a href=\"https:\/\/devblogs.microsoft.com\/surface-duo\/dual-screen-web-game-foldship\/?WT.mc_id=blog-surfaceduoblog-aapowell\">using standard web technologies<\/a>. With the web being as complex a platform as it is, many developers prefer to use frameworks and libraries to help them build rich, complex responsive web applications.\n<\/p>\n<p>\n  In this post, we\u2019ll look at how we can use one of the most popular libraries, <a href=\"https:\/\/reactjs.org\/\">React<\/a>, to build a foldable web experience. We\u2019ll take the <a href=\"https:\/\/github.com\/foldable-devices\/demos\/tree\/master\/photo-gallery\">photo gallery demo<\/a> and make a React version of it, then look at how we can leverage the power of React to make a foldable experience.\n<\/p>\n<p>\n  <img decoding=\"async\" width=\"1725\" height=\"806\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/01\/a-picture-containing-text-different-description.gif\" class=\"wp-image-1225\" alt=\"Animation showing photo gallery demo app with developer tools\" \/><br\/><em>Figure 1: Animation of the photo gallery and web developer tools (click to start)<\/em>\n<\/p>\n<h2>Creating the demo<\/h2>\n<p>\n  To keep the demo application as simple as possible, we\u2019ve used <a href=\"https:\/\/create-react-app.dev\/\">Create React App<\/a> to scaffold up the application, using the <a href=\"https:\/\/create-react-app.dev\/docs\/adding-typescript\/\">TypeScript Template<\/a> to give us type-safety in the codebase.\n<\/p>\n<p>\n  The full source code of the demo, which has converted the existing photo gallery to a React application, can be found <a href=\"https:\/\/github.com\/aaronpowell\/surface-duo-photo-gallery\">on GitHub<\/a>, but let\u2019s look at a few specific parts of it.\n<\/p>\n<h2>App.tsx<\/h2>\n<p>\n  This is the entry point for our application and contains some state for which image is selected presently, then adds components to the component tree representing how our application works.\n<\/p>\n<pre>import React, { useState } from \"react\";\r\nimport images, { Image } from \".\/images\";\r\nimport \".\/App.css\";\r\nimport Gallery from \".\/Gallery\";\r\nimport Details from \".\/DetailContainer\";\r\nimport Fold from \".\/Fold\";\r\nimport FullView from \".\/FullView\";\r\nimport { Container } from \".\/App.styles\";\r\n\r\nfunction App() {\r\n  const [currentImage, setCurrentImage] = useState&lt;Image>();\r\n\r\n  return (\r\n    &lt;Container>\r\n      &lt;Gallery images={images} selectImage={setCurrentImage} \/>\r\n      &lt;Fold \/>\r\n      &lt;Details currentImage={currentImage} \/>\r\n      &lt;FullView\r\n        currentImage={currentImage}\r\n        closeImage={() => setCurrentImage(undefined)}\r\n        prevImage={(image) =>\r\n          setCurrentImage(images[images.indexOf(image) - 1])\r\n        }\r\n        nextImage={(image) =>\r\n          setCurrentImage(images[images.indexOf(image) + 1])\r\n        }\r\n      \/>\r\n    &lt;\/Container>\r\n  );\r\n}\r\n\r\nexport default App;<\/pre>\n<p>\n  Straight away we can see what the application is doing succinctly, we have a Container component which holds the application, and within that there are four components, Gallery, Fold, Details and FullView.\n<\/p>\n<p>\n  Let\u2019s have a look at the Container component to see how we\u2019ve styled it.\n<\/p>\n<pre>import styled from \"styled-components\";\r\n\r\nexport const Container = styled.div`\r\n  display: flex;\r\n  flex-direction: row;\r\n\r\n  @media (screen-spanning: single-fold-vertical) {\r\n    flex-direction: row;\r\n  }\r\n\r\n  @media (screen-spanning: single-fold-horizontal) {\r\n    flex-direction: column-reverse;\r\n  }\r\n\r\n  @media (screen-spanning: none) {\r\n    flex-direction: row;\r\n  }\r\n`;<\/pre>\n<p>\n  We\u2019re using <a href=\"https:\/\/styled-components.com\/\">Styled Components<\/a>, a popular library for doing CSS-in-JavaScript and component styling. We can see that in the styles we\u2019re applying, the appropriate media queries exist to make the application responsive across a single and dual-screen device. This means that our component is ready to go when it comes to working on a dual-screen device.\n<\/p>\n<h2>Fold, Details, and FullView<\/h2>\n<p>\n  These three components are used for different display options, depending on whether we\u2019re on a single or dual-screen device. For example, we don\u2019t need to include the Fold if we\u2019re on a single screen, as the screen doesn\u2019t have a fold, so we can use CSS to hide it.\n<\/p>\n<pre>import styled from \"styled-components\";\r\n\r\nconst Fold = styled.div`\r\n  background-size: 40px 40px;\r\n\r\n  background-color: #737373;\r\n  background-image: linear-gradient(\r\n    45deg,\r\n    rgba(255, 255, 255, 0.2) 25%,\r\n    transparent 25%,\r\n    transparent 50%,\r\n    rgba(255, 255, 255, 0.2) 50%,\r\n    rgba(255, 255, 255, 0.2) 75%,\r\n    transparent 75%,\r\n    transparent\r\n  );\r\n\r\n  @media (screen-spanning: single-fold-vertical) {\r\n    height: env(fold-height);\r\n    width: env(fold-width);\r\n  }\r\n\r\n  @media (screen-spanning: single-fold-horizontal) {\r\n    height: env(fold-height);\r\n    width: env(fold-width);\r\n  }\r\n\r\n  @media (screen-spanning: none) {\r\n    height: 0;\r\n    width: 0;\r\n  }\r\n`;\r\n\r\nexport default Fold;<\/pre>\n<p>\n  As we can see here, there\u2019s a lot of CSS being loaded when we\u2019re going to end up hiding the element when it matches @media\u00a0(screen-spanning:\u00a0none). We have used a similar approach on the Details component, and the inverse for FullView, as it is hidden when we are in dual-screen mode.\n<\/p>\n<p>\n  With all this in place, our application is ready to go, and you can view it <a href=\"https:\/\/kind-beach-0f1a1a11e.azurestaticapps.net\/\">here<\/a>.\n<\/p>\n<p>\n  But we\u2019re not leveraging the real power of React, in fact, we\u2019re doing a bit of a disservice to React as we\u2019re adding components to the component tree that don\u2019t need to be there. Let\u2019s look at the component structure:\n<\/p>\n<p>\n  <img decoding=\"async\" width=\"1232\" height=\"638\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/01\/graphical-user-interface-application-description.png\" class=\"wp-image-1226\" alt=\"Developer tools window showing page structure\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/01\/graphical-user-interface-application-description.png 1232w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/01\/graphical-user-interface-application-description-300x155.png 300w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/01\/graphical-user-interface-application-description-1024x530.png 1024w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/01\/graphical-user-interface-application-description-768x398.png 768w\" sizes=\"(max-width: 1232px) 100vw, 1232px\" \/><br\/><em>Figure 2: React component structure<\/em>\n<\/p>\n<p>\n  You\u2019ll notice that we\u2019re adding both the Details and FullView components (and Fold for that matter), regardless of whether it\u2019s needed or not, then relying on CSS to hide\/show the right version. While this might not be an issue in our demo application, in a more complex application, this could manifest itself as a performance issue as we\u2019re rendering components unnecessarily.\n<\/p>\n<h2>Introducing react-foldable<\/h2>\n<p>\n  To make it easy to work with foldable displays from React, we can use the <a href=\"https:\/\/www.npmjs.com\/package\/@aaronpowell\/react-foldable\">react-foldable npm package<\/a>, which will provide us with components and hooks which expose the information from the underlying DOM APIs.\n<\/p>\n<h3>Creating a foldable region<\/h3>\n<p>\n  Because part of our application may always be available regardless of whether we have a single or dual-screen experience, such as the Gallery, react-foldable gives us a component that allows us to make only part of the application \u201cfoldable aware\u201d.\n<\/p>\n<p>\n  Let\u2019s revisit the App.tsx file and have it use the Foldable component:\n<\/p>\n<pre>import { Foldable } from \"@aaronpowell\/react-foldable\";\r\n\r\nfunction App() {\r\n  const [currentImage, setCurrentImage] = useState&lt;Image>();\r\n\r\n  return (\r\n    &lt;Container>\r\n      &lt;Gallery images={images} selectImage={setCurrentImage} \/>\r\n      &lt;Fold \/>\r\n      &lt;Details currentImage={currentImage} \/>\r\n      &lt;FullView\r\n        currentImage={currentImage}\r\n        closeImage={() => setCurrentImage(undefined)}\r\n        prevImage={(image) =>\r\n          setCurrentImage(images[images.indexOf(image) - 1])\r\n        }\r\n        nextImage={(image) =>\r\n          setCurrentImage(images[images.indexOf(image) + 1])\r\n        }\r\n      \/>\r\n      &lt;Foldable>\r\n        \r\n      &lt;\/Foldable>\r\n    &lt;\/Container>\r\n  );\r\n}\r\n\r\nexport default App;<\/pre>\n<p>\n  The Foldable component itself doesn\u2019t change how our application works, we do that by adding FoldableScreen components to it. The FoldableScreen component is a wrapper around parts of our component tree that will be conditionally added or removed, depending on a test we provide against the foldable state of the application.\n<\/p>\n<p>\n  Let\u2019s make it so the Fold and Details components are not included unless we\u2019ve spanned across to a second screen:\n<\/p>\n<pre>return (\r\n    &lt;Container>\r\n      &lt;Gallery images={images} selectImage={setCurrentImage} \/>\r\n      &lt;FullView\r\n        currentImage={currentImage}\r\n        closeImage={() => setCurrentImage(undefined)}\r\n        prevImage={(image) =>\r\n          setCurrentImage(images[images.indexOf(image) - 1])\r\n        }\r\n        nextImage={(image) =>\r\n          setCurrentImage(images[images.indexOf(image) + 1])\r\n        }\r\n      \/>\r\n      &lt;Foldable>\r\n        &lt;FoldableScreen matchScreen={1}>\r\n          &lt;React.Fragment>\r\n            &lt;Fold \/>\r\n            &lt;Details currentImage={currentImage} \/>\r\n          &lt;\/React.Fragment>\r\n        &lt;\/FoldableScreen>\r\n      &lt;\/Foldable>\r\n    &lt;\/Container>\r\n  );<\/pre>\n<p>\n  On FoldableScreen we can provide a matchScreen prop, which takes a number that matches a segment in the windowSegments data. It\u2019s important to remember that this is working from a zero-based index, so the \u201csecond screen\u201d is the screen segment in position 1 of the array.\n<\/p>\n<p>\n  Next, we want to hide the FullView component if we\u2019re not spanned across multiple screens, and we can\u2019t do that by providing a matchScreen value of 0 as there\u2019s always a screen there, we instead can provide a match function.\n<\/p>\n<pre>return (\r\n    &lt;Container>\r\n      &lt;Gallery images={images} selectImage={setCurrentImage} \/>\r\n\r\n      &lt;Foldable>\r\n        &lt;FoldableScreen matchScreen={1}>\r\n          &lt;React.Fragment>\r\n            &lt;Fold \/>\r\n            &lt;Details currentImage={currentImage} \/>\r\n          &lt;\/React.Fragment>\r\n        &lt;\/FoldableScreen>\r\n\r\n        &lt;FoldableScreen match={({ isDualScreen }) => !isDualScreen}>\r\n          &lt;FullView\r\n            currentImage={currentImage}\r\n            closeImage={() => setCurrentImage(undefined)}\r\n            prevImage={(image) =>\r\n              setCurrentImage(images[images.indexOf(image) - 1])\r\n            }\r\n            nextImage={(image) =>\r\n              setCurrentImage(images[images.indexOf(image) + 1])\r\n            }\r\n          \/>\r\n        &lt;\/FoldableScreen>\r\n      &lt;\/Foldable>\r\n    &lt;\/Container>\r\n  );<\/pre>\n<p>\n  The match prop takes a function which receives an argument that has three properties, isDualScreen, windowSegments (the array of DOMRect), and screenSpanning (is the screen spanned as vertical, horizontal, or none, matching the media query). If we\u2019re not in dual-screen mode we can render the component, so that\u2019s what we\u2019ll return from our function.\n<\/p>\n<p>\n  Now when we run our application and look at the component tree, we\u2019ll notice that it starts a lot smaller as there\u2019s no selected image, and as we transition between single and dual-screen, the components loaded are going to change.\n<\/p>\n<p>\n  <img decoding=\"async\" width=\"1725\" height=\"806\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/01\/a-screenshot-of-a-computer-description-automatica.gif\" class=\"wp-image-1227\" alt=\"Animation of the photo gallery sample using React and the developer tools\" \/><br\/><em>Figure 3: Animation of the photo gallery using react-foldable (click to start)<\/em>\n<\/p>\n<h2>Conclusion<\/h2>\n<p>\n  Here we\u2019ve seen how we can leverage the power of React to make a foldable web experience and use the react-foldable library to expose the important information about the underlying foldable state of an application to have our component tree only represent what our application needs.\n<\/p>\n<p>\n  You can view the <a href=\"https:\/\/kind-beach-0f1a1a11e.azurestaticapps.net\/\">initial application<\/a> (<a href=\"https:\/\/github.com\/aaronpowell\/surface-duo-photo-gallery\">code<\/a>), and the <a href=\"https:\/\/kind-beach-0f1a1a11e-1.westus2.azurestaticapps.net\/\">updated version using react-foldable<\/a> (<a href=\"https:\/\/github.com\/aaronpowell\/surface-duo-photo-gallery\/tree\/react-foldable\">code<\/a>).\n<\/p>\n<p>\n  If you want to learn more about the react-foldable library, check out the <a href=\"https:\/\/github.com\/aaronpowell\/react-foldable\">GitHub repository<\/a>. For more information on dual-screen web development, visit the <a href=\"https:\/\/docs.microsoft.com\/dual-screen\/web\/?WT.mc_id=docs-surfaceduoblog-aapowell\">dual-screen web developer documentation<\/a>.\n<\/p>\n<p>\n  The Surface Duo Developer Experience team is keen to hear about your experiences building dual-screen web apps &#8211; reach out via our <a href=\"https:\/\/techcommunity.microsoft.com\/t5\/surface-duo-sdk\/bd-p\/SurfaceDuoSDK\">forum<\/a> or on Twitter <a href=\"https:\/\/twitter.com\/surfaceduodev\">@surfaceduodev<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hello web developers! Responsive design has always been the cornerstone of web development, rather than designing for a single use case, we focus on creating applications that can adjust to the needs of the platform they are running on. Microsoft Surface Duo is no exception to this and in past blog posts we\u2019ve explored how [&hellip;]<\/p>\n","protected":false},"author":40333,"featured_media":1237,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[412,711,46,413,530],"class_list":["post-1224","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-surface-duo-sdk","tag-javascript","tag-react","tag-surface-duo","tag-typescript","tag-web"],"acf":[],"blog_post_summary":"<p>Hello web developers! Responsive design has always been the cornerstone of web development, rather than designing for a single use case, we focus on creating applications that can adjust to the needs of the platform they are running on. Microsoft Surface Duo is no exception to this and in past blog posts we\u2019ve explored how [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts\/1224","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/users\/40333"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/comments?post=1224"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts\/1224\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/media\/1237"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/media?parent=1224"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/categories?post=1224"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/tags?post=1224"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}