<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lucian Boaca</title>
    <description>The latest articles on DEV Community by Lucian Boaca (@lucianbc).</description>
    <link>https://dev.to/lucianbc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F182848%2Fbe385b63-3d4d-4b4a-ad35-26d96e79a71c.jpg</url>
      <title>DEV Community: Lucian Boaca</title>
      <link>https://dev.to/lucianbc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lucianbc"/>
    <language>en</language>
    <item>
      <title>Building a responsive, horizontal photo grid</title>
      <dc:creator>Lucian Boaca</dc:creator>
      <pubDate>Sat, 21 Aug 2021 16:20:00 +0000</pubDate>
      <link>https://dev.to/lucianbc/building-a-responsive-horizontal-photo-grid-14kn</link>
      <guid>https://dev.to/lucianbc/building-a-responsive-horizontal-photo-grid-14kn</guid>
      <description>&lt;p&gt;When I set to build a webpage for my photography, laying out the photos in a grid was the biggest challenge. It turns out displaying photos of different aspect ratios in a grid is not that simple and there are a few trade-offs that have to be made. In this post, I describe the way I implemented such a layout. To see the result, check out my &lt;a href="https://photos.lucianbc.com/"&gt;photography page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem description
&lt;/h2&gt;

&lt;p&gt;We want to display multiple photos in something similar to feed, with multiple photos per row. We also want this grid to be responsive, meaning that on wider devices it shows multiple photos, while on mobile phones it shows one or a maximum of two photos per row. The photos should have some space between them, let's say 10 pixels.&lt;/p&gt;

&lt;p&gt;As for the layout, we want the photos to be displayed in something that could be called a staggered horizontal grid, meaning a variable number of photos per row. I like how this layout creates a brick-wall pattern, accomodating both landscape and portrait photos and keeping straight rows and a left-to-right focus. However, making this layout responsive, fast and with no javascript at runtime required a few tricks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--e3jgBf4k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1vj5dguou8tkfboetkea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--e3jgBf4k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1vj5dguou8tkfboetkea.png" alt="Horizontal Staggered Grid"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Horizontal Staggered Grid&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementation details
&lt;/h2&gt;

&lt;p&gt;This layout was popularised by apps such as &lt;a href="https://www.flickr.com/search/?text=landscape"&gt;flickr&lt;/a&gt; and &lt;a href="https://500px.com/search?q=landscape&amp;amp;type=photos&amp;amp;sort=relevance"&gt;500px&lt;/a&gt;. Flicker open-sourced their algorithm for creating the photo grid on their website and wrote a blog post about it &lt;a href="https://code.flickr.net/2016/04/05/our-justified-layout-goes-open-source/"&gt;here&lt;/a&gt;. I based my solution on their algorithm, but I won't go into details of how it works. Their observation was that you can't build a photo grid with a consistent row height, while also accomodating a collection of photos with arbitrary aspect ratios and in an arbitrary order. However, you don't need consistent row heights for the layout to look good, and laying out photos one by one, deciding whether to add the current photo to the last row or create a new one leads to a good enough solution.&lt;/p&gt;

&lt;p&gt;Simply using this algorithm and also having the layout being responsive requires running it on every window resize. It also has the typical problem with client-side rendering - displaying the photos only after the initial page renders and having to support an initial, empty state. I wanted my page to be fast and server-side rendered.&lt;/p&gt;
&lt;h3&gt;
  
  
  Pre-rendering layouts
&lt;/h3&gt;

&lt;p&gt;To get past the initial load issue, I figured out I can run the Flickr algorithm at build time. Moreover, to make the layout responsive, I can create multiple configurations for the Flickr algorithm, one for each media query that I want to support. These configurations specify a layout width and the desired height and yield an array of boxes with absolute values (in pixels) for the width and height of each photo in my collection. To make the photos resizable, I can divide the width of each box to the container width how much per cent of the full width each photo should take. Then I render each photo in a &lt;code&gt;flexbox&lt;/code&gt; container and with the &lt;code&gt;flex: 0 0 ${box.widthPercent}&lt;/code&gt; CSS property. I used &lt;a href="https://www.gatsbyjs.com/"&gt;gatsby&lt;/a&gt; to build my page, which allowed me to use react and &lt;a href="https://styled-components.com/"&gt;styled-components&lt;/a&gt; to interpolate the javascript-computed &lt;code&gt;box.widthPercent&lt;/code&gt; into CSS media-queries and have all of this logic run at built time. The simplified code that implements what is described above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// import the Flickr algorithm&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;layout&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;justified-layout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;breakPoints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;// media query is null for the default style&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;mediaQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;containerWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;mediaQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;568&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;containerWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// add more if needed&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PhotoGrid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;photos&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ratios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aspectRatio&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;layouts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;breakPoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;breakPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;breakPoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ratios&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;boxSpacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;containerPadding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;containerWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;breakPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;containerWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getRatiosAndBreakpointsForPhoto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;photoIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// returns an array containing the media query and the width percentage the photo should take on that media query&lt;/span&gt;
    &lt;span class="c1"&gt;// example: [{ mediaQuery: null, ratio: 0.3 }, { mediaQuery: null, ratio: 0.8 }]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;layouts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;mediaQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;breakPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mediaQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;boxes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;photoIndex&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;breakPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;containerWidth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
        &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;flexWrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wrap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PhotoBox&lt;/span&gt; &lt;span class="nx"&gt;responsiveRatios&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;getRatiosAndBreakpointsForPhoto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ImageComponent&lt;/span&gt; &lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/PhotoBox&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;      &lt;span class="p"&gt;})}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flexCssValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;photoBreakPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;photoBreakPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mediaQuery&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`flex: 0 0 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;photoBreakPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%;`&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`@media (min-width: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;photoBreakPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;breakpoint&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px) {
        flex: 0 0 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;photoBreakPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%;
      }`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PhotoBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;styled&lt;/span&gt;&lt;span class="s2"&gt;`
  &lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responsiveRatios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;flexCssValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="s2"&gt;
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the media queries are written mobile-first, meaning the default, non &lt;code&gt;@media&lt;/code&gt; annotated flex value is the one that should be rendered on a mobile device. To give the default configuration in our &lt;code&gt;breakPoints&lt;/code&gt; array, we simply set it to null. As an interesting observation, &lt;code&gt;styled-components&lt;/code&gt; allows injecting javascript values into the resulting stylesheet at build time. This is supported by the &lt;code&gt;gatsby-plugin-styled-components&lt;/code&gt; plugin. Make sure to check the &lt;a href="https://www.gatsbyjs.com/docs/how-to/styling/styled-components/"&gt;official guide&lt;/a&gt; to get styled-components in gatsby. The &lt;code&gt;ImageComponent&lt;/code&gt; is not implemented - it could be as basic as an &lt;code&gt;&amp;lt;img style={{width: '100%}} /&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;

&lt;p&gt;In this way, we pushed computing the actual image size to the browser's CSS engine rather than us reacting to viewport changes. The media queries enable us to switch the layout based on the viewport size.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gap between photos
&lt;/h3&gt;

&lt;p&gt;Adding a gap between photos will require a new tradeoff with the above approach. The Flickr justified layout algorithm can be configured with the &lt;code&gt;boxSpacing&lt;/code&gt; and &lt;code&gt;containerPadding&lt;/code&gt; properties and they work great when running the algorithm with the actual &lt;code&gt;containerWidth&lt;/code&gt;. However, the above approach picks a handful of reference container widths and renders the in-between widths as percentages based on these references. If we keep the spacing fixed and increase the actual container width to be bigger than the active reference width, the photos on any giver row will grow by a different factor because the spacing stays fixed. Moreover, given that the photos have different aspect ratios, the heights will go out of sync and small misalignments will appear. I won't include the math because it's quite boring.&lt;/p&gt;

&lt;p&gt;To fix this issue, a slight crop is introduced. We compute the layout as though there is no padding and let the &lt;code&gt;PhotoBox&lt;/code&gt;es fill the rows with no spacing. Then, in CSS, we make them &lt;code&gt;box-sizing: border-box&lt;/code&gt; and add some padding. For a spacing of &lt;code&gt;10px&lt;/code&gt;, we add to each box a padding of &lt;code&gt;5px&lt;/code&gt;. Then, to not stretch the image in weird ways and to also keep the gaps consistent, we set the &lt;code&gt;object-fit: cover&lt;/code&gt; CSS property on the image. You can read more about it &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit"&gt;here&lt;/a&gt;, but long story short, it sizes up the image to fill the container and clips whatever is out of the bounds of the container. This approach paired with granular enough media queries ensures that whenever a layout will require too much of a crop from the photos, photos will be added or removed from each row to keep them looking good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This was my approach to displaying a pre-rendered, vertical staggered photo grid. I am content with its trade-offs and technical complexity. There is probably a way to achieve this with pure CSS and no cropping and I've read a guide to such an implementation &lt;a href="https://github.com/xieranmaya/blog/issues/6"&gt;here&lt;/a&gt;. I will probably dive deeper into this at some later point, but in the meantime, I'm focusing on further developing my photos webpage. I will document here any other interesting challenge I might find.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>gatsby</category>
      <category>react</category>
    </item>
    <item>
      <title>Union Type Merging in Typescript</title>
      <dc:creator>Lucian Boaca</dc:creator>
      <pubDate>Fri, 04 Jun 2021 16:15:15 +0000</pubDate>
      <link>https://dev.to/lucianbc/union-type-merging-in-typescript-9al</link>
      <guid>https://dev.to/lucianbc/union-type-merging-in-typescript-9al</guid>
      <description>&lt;p&gt;Typescript unions can be very useful in a lot of cases. They can be used to implement &lt;a href="https://stackoverflow.com/questions/33915459/algebraic-data-types-in-typescript#:~:text=TypeScript%202.0%20has%20support%20for,unions%20or%20algebraic%20data%20types."&gt;algebraic data types&lt;/a&gt; when having a &lt;a href="https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions"&gt;discriminator&lt;/a&gt; key. Without such a discriminator, they can be quite limiting. This guide describes merging an object union - another pattern that can be used to enhance type union usability.&lt;/p&gt;

&lt;p&gt;TL;DR; This article shows you how to merge a type union (of objects) into a single object type. Find all the steps in the typescript playground &lt;a href="https://www.typescriptlang.org/play?ts=4.2.3#code/C4TwDgpgBMCMUF4oG8CwAoKUCGAuKAzsAE4CWAdgOYDcGWARvuQK4C29ExtmUEA-PiJkq3AL7cMoSDABMiFHRyCSFGooDGTNhy6L+y4WvTiMk8NGABmeWh54oLdp25YAJgdUuojQipF6Pf2MJdCkLAgIbRXshVSgAHwdtZ0V6ASSnYgSoZnJXCAAzCghXL3V0xx1s3Pyi8hKvVwrkrMSawuLSvXTYqmq8jvqu41NQ8xhIpDhs4DlEqxCwqABhAHtWVlXyAGkIEAIAHgAVXgAPYAg8yNX6ACsIdWAAPnkAaz3Vgqgj7iWAQQANgDdvtji8kCcIOdLq5IthyCAoHwoO8QJ9vlAmBAAG4pMbSADKzHoJGwjwOfwANCtwVA-mcLlcVkiHDjOJi6b9xgA5LZrDZbEGHSHQpk3e6PWlEknEMnAClAoVg6n8zY7PbCp5PRbjAAKpHUryO5mO1O2DJhkUBwI1YNpIsZsJQUAA2q8oBQoNsALrpeGI0Qso4un0c9p1BqjJbYd1Ia1K4AEbVmaSG+SqwW2xPJ-HQcixqC88gZ9Wg7M66TYYDyfWG42QA6J6kAcmwze1UAA9J3eNjsMwAVWIJFgKtfIZspVOCnoCSawajSam1Bm-R29Quz2cf3BxcR2PepRJy1+rVOjOoI953WlwQW+p15vezuh-uMlU2gMI64L65q0ha0XBtl2bVxH27Z8B1fGADz8I9PzPIYLwgf8oEA+sIEbO8VwgcCtz7KC9xg8c4gQwYSijcYAFlOEoTCHUtKBxQeZ4oh4N0PXIFZ1jVJUjieb18HQ8wAHkClNFEngwQMADIFHY91PSLEs+IE9JhMgMSJNeKTgko6QNIgLSjjNC0mUPY9MmyAgQHYVYAVpc0oUdK1FVtfiWUMiTtheLFcV0dAL1QmjiDorCcwvU55GAF1W2bb0QnULYiBgPsAVgfBUNsLB7GbWAZEsAAWZtKVSfB8ssUq9MC9AkvIFLgDSmRMrYnL8DygriqqrBNCgCrCqqkwarqhq0ssFqkGypQ+oK7qoHcFcAE5LBKsqVxWuaIHa5tpMS5LqwAC3hVwAU4AAxXJHlILZ5AACka7AAUygBKRAXimkbq2waK0oAOmwMp9u8H7Ht++hAfq6t1BBgFfvUCGUtcGHfuGHqgegKY-ogEI0fq+yIBRiB6GYShbubAB3A7LkvZK2DiYAqagNLmAgakAEkoDo4ASpwal6GpdRqVcakIGenGYHGRNWqwaaYri705oYTLYrXBXFB65XmwfNWeDcTWwJ1mXeE13DDagURdtGI68lO4gLvIK6tnutLYDFjBrZO87LuAa7yGdx6ZDd9APdt+3Hb9h6AUsIPRk+wh4EmxRmMeDD2oIWBVp4ZmIFTqBluknBIk+vbIcIORE54ZPgFz5sCBkTOsGYAhODZ8gwGYYB2sWmQM4L7Ai-2itoEmCXIHRdOZnMceZBLlKQ4gAk7uz-AQrCpNXoQd7FFIL5buz36q4wxAECQWuM9eqbcZSqKkH37OMK8K-8cJ4nSebDnKFWL6uIn8nSAZnmpwg5YEDBAAEzd5JGzjoiW+j0Wa-Sbi3NuHdH40zxqdF+JMyYfy-jgH+cg-4AOpCAYB5tpKjAvAAL2lvoEiQQhpBQxlAShsVTbUCAA"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're building
&lt;/h2&gt;

&lt;p&gt;Suppose we have the following types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;t1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;e&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;t2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;e&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;t3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;t2&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;t3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want to process these types and end up with something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;b&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;c&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;d&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;e&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why would we want such a type?
&lt;/h3&gt;

&lt;p&gt;Let's suppose that these 3 types represent some configuration object and we have a variable that can be assigned to an object of any of the 3 types. We want to use this variable to handle this configuration. A natural representation for the type of this variable is &lt;code&gt;type ts = t1 | t2 | t3&lt;/code&gt; which would read as &lt;code&gt;t1&lt;/code&gt; &lt;strong&gt;or&lt;/strong&gt; &lt;code&gt;t2&lt;/code&gt; &lt;strong&gt;or&lt;/strong&gt; &lt;code&gt;t3&lt;/code&gt;. But in typescript, unions are a bit more complicated. You would not be able to access any other keys other than the common ones between the 3 types. That is because if a variable is either one of the 3 types, typescript is only able to guarantee that the object you are handling has the common keys between them, which would just be &lt;code&gt;a&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This limitation can of course be circumvented with some type discriminators, be they a discriminating key or type guards. You can read more about this pattern &lt;a href="https://thoughtbot.com/blog/the-case-for-discriminated-union-types-with-typescript"&gt;here&lt;/a&gt;. Most of the use-cases will be covered by this approach, but sometimes we can't really distinguish between the types in our union and we have to handle all the possible cases.&lt;/p&gt;

&lt;p&gt;Now let's think about what such a type union could mean. From the point of view of a function that receives such an object, it could mean that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the key &lt;code&gt;a&lt;/code&gt; is defined and it could be a &lt;code&gt;string&lt;/code&gt; or a &lt;code&gt;number&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;the keys &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt;, &lt;code&gt;d&lt;/code&gt; might or might not be defined, depending on the instantiation of the object, so the function must be aware of this before accessing the keys;&lt;/li&gt;
&lt;li&gt;the key &lt;code&gt;e&lt;/code&gt; might or might not be defined, since it appears in all the types but only &lt;code&gt;t3&lt;/code&gt; requires it, so the function must be aware of this as well;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These requirements describe our target type &lt;code&gt;t&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Common Keys
&lt;/h3&gt;

&lt;p&gt;Let's first figure out the common keys of a type union. That is, given an object type union, we want all of the keys that typescript allows us to access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CommonKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CommonKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// evaluates to "a" | "e"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  All Keys
&lt;/h3&gt;

&lt;p&gt;Then, let's figure out all of the keys that the union contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AllKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;AllKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// evaluates to "a" | "b" | "e" | "c" | "d"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This type is using the &lt;a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types"&gt;distributive conditional types&lt;/a&gt; feature to pick up all the keys. I imagine this operation as &lt;code&gt;map&lt;/code&gt;-ing what is after &lt;code&gt;?&lt;/code&gt; over each type in the union and then &lt;code&gt;join&lt;/code&gt;-ing the result with &lt;code&gt;|&lt;/code&gt; in between. So in the case of &lt;code&gt;ak&lt;/code&gt; it could be described as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;t1 extends any ? (keyof t1) : never | t2 extends any ? (keyof t2) : never | t3 extends any ? (keyof t3) : never

&amp;lt;=&amp;gt;

(keyof t1) | (keyof t2) | (keyof t3)

&amp;lt;=&amp;gt;

("a" | "b" | "e") | ("a" | "c" | "e") | ("a" | "d" | "e")

&amp;lt;=&amp;gt;

"a" | "b" | "e" | "c" | "d"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This feature will be used in the next steps as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Non Common Keys
&lt;/h3&gt;

&lt;p&gt;The next step is to figure out the keys that are not common between the types in the union. To figure this out, we simply need to subtract the common keys from all of the keys of the given type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Subtract&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;C&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NonCommonKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Subtract&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AllKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CommonKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;nk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;NonCommonKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// evaluates to "b" | "c" | "d"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The type &lt;code&gt;Subtract&lt;/code&gt; takes two type parameters, &lt;code&gt;A&lt;/code&gt; for all the keys and &lt;code&gt;C&lt;/code&gt; for the common keys and it basically iterates over all the values in &lt;code&gt;A&lt;/code&gt; using the distributivity mentioned above and for each item in the union, if it is in &lt;code&gt;C&lt;/code&gt;, then returns &lt;code&gt;never&lt;/code&gt;, otherwise it returns the item.&lt;/p&gt;

&lt;p&gt;Note that &lt;code&gt;type l1 = "a"&lt;/code&gt; extends &lt;code&gt;type l2 = "a" | "b"&lt;/code&gt;. That makes complete sense since, by definition, a type &lt;code&gt;l1&lt;/code&gt; extends a type &lt;code&gt;l2&lt;/code&gt; if all the values that are assignable to &lt;code&gt;l1&lt;/code&gt; are assignable to &lt;code&gt;l2&lt;/code&gt; as well. If you find that the strings &lt;code&gt;"a"&lt;/code&gt; and &lt;code&gt;"b"&lt;/code&gt; are types, find out more in the typescript docs about &lt;a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types"&gt;literal types&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;NonCommonKeys&lt;/code&gt; type is just a nicer interface so that the original parameter is our type union and not keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Figure out the type of a given key
&lt;/h3&gt;

&lt;p&gt;Now we want to be able to figure out what all the possible types of a given key are in a given type union. That is, given a type union &lt;code&gt;T&lt;/code&gt; and a key &lt;code&gt;K&lt;/code&gt;, if that key is present in an object in the union, return its type, otherwise return &lt;code&gt;undefined&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PickType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;AllKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="p"&gt;]?:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PickType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// evaluates to string | number&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;bt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PickType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// evaluates to string | number | undefined&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PickType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;c&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// evaluates to number | undefined&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PickType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// evaluates to string | undefined&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;et&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PickType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;e&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// evaluates to string | undefined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that for a key that is present in all types, &lt;code&gt;undefined&lt;/code&gt; will not be added to the mix. For keys that are missing in at least one type, the &lt;code&gt;undefined&lt;/code&gt; will be present in the resulting union.&lt;/p&gt;

&lt;p&gt;Also, note that the condition says that &lt;code&gt;T&lt;/code&gt; must extend an object with the key &lt;code&gt;k&lt;/code&gt; that can be optional, so that optionals are picked up correctly. Also, from the way typescript evaluates the type of an optional key, it will be marked as undefined.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;te&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;e&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// evaluates to string | undefined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Merge
&lt;/h3&gt;

&lt;p&gt;Now we have all the ingredients to merge the original type union and create our desired type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Merge&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;CommonKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;PickTypeOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;NonCommonKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]?:&lt;/span&gt; &lt;span class="nx"&gt;PickTypeOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PickTypeOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;AllKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;PickType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Merge&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we're doing is basically composing the type so that each key in the common keys is required and each key in the non common keys is optional. The &lt;code&gt;PickTypeOf&lt;/code&gt; helper is just a proxy for &lt;code&gt;PickType&lt;/code&gt; described above, but it was needed because typescript was not recognising the &lt;code&gt;k&lt;/code&gt; in the expression above as extending &lt;code&gt;AllKeys&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The resulting type &lt;code&gt;t&lt;/code&gt; is quite verbose on typescript v4.2, which introduced some algorithms to improve how types are displayed and even more verbose on versions prior to that. Feel free to check it out by cmd+hovering over it in the &lt;a href="https://www.typescriptlang.org/play?ts=4.2.3&amp;amp;ssl=55&amp;amp;ssc=1&amp;amp;pln=56&amp;amp;pc=1#code/C4TwDgpgBMCMUF4oG8CwAoKUCGAuKAzsAE4CWAdgOYDcGWARvuQK4C29ExtmUEA-PiJkq3AL7cMoSDABMiFHRyCSFGooDGTNhy6L+y4WvTiMk8NGABmeWh54oLdp25YAJgdUuojQipF6Pf2MJdCkLAgIbRXshVSgAHwdtZ0V6ASSnYgSoZnJXCAAzCghXL3V0xx1s3Pyi8hKvVwrkrMSawuLSvXTYqmq8jvqu41NQ8xhIpDhs4DlEqxCwqABhAHtWVlXyAGkIEAIAHgAVXgAPYAg8yNX6ACsIdWAAPnkAaz3Vgqgj7iWAQQANgDdvtji8kCcIOdLq5IthyCAoHwoO8QJ9vlAmBAAG4pMbSADKzHoJGwjwOfwANCtwVA-mcLlcVkiHDjOJi6b9xgA5LZrDZbEGHSHQpk3e6PWlEknEMnAClAoVg6n8zY7PbCp5PRbjAAKpHUryO5mO1O2DJhkUBwI1YNpIsZsJQUAA2q8oBQoNsALrpeGI0Qso4un0c9p1BqjJbYd1Ia1K4AEbVmaSG+SqwW2xPJ-HQcixqC88gZ9Wg7M66TYYDyfWG42QA6J6kAcmwze1UAA9J3eNjsMwAVWIJFgKtfIZspVOCnoCSawajSam1Bm-R29Quz2cf3BxcR2PepRJy1+rVOjOoI953WlwQW+p15vezuh-uMlU2gMI64L65q0ha0XBtl2bVxH27Z8B1fGADz8I9PzPIYLwgf8oEA+sIEbO8VwgcCtz7KC9xg8c4gQwYSijcYAFlOEoTCHUtKBxQeZ4oh4N0PXIFZ1jVJUjieb18HQ8wAHkClNFEngwQMADIFHY91PSLEs+IE9JhMgMSJNeKTgko6QNIgLSjjNC0mUPY9MmyAgQHYVYAVpc0oUdK1FVtfiWUMiTtheLFcV0dAL1QmjiDorCcwvU55GAF1W2bb0QnULYiBgPsAVgfBUNsLB7GbWAZEsAAWZtKVSfB8ssUq9MC9AkvIFLgDSmRMrYnL8DygriqqrBNCgCrCqqkwarqhq0ssFqkGypQ+oK7qoHcFcAE5LBKsqVxWuaIHa5tpMS5LqwAC3hVwAU4AAxXJHlILZ5AACka7AAUygBKRAXimkbq2waK0oAOmwMp9u8H7Ht++hAfq6t1BBgFfvUCGUtcGHfuGHqgegKY-ogEI0fq+yIBRiB6GYShbubAB3A7LkvZK2DiYAqagNLmAgakAEkoDo4ASpwal6GpdRqVcakIGenGYHGRNWqwaaYri705oYTLYrXBXFB65XmwfNWeDcTWwJ1mXeE13DDagURdtGI68lO4gLvIK6tnutLYDFjBrZO87LuAa7yGdx6ZDd9APdt+3Hb9h6AUsIPRk+wh4EmxRmMeDD2oIWBVp4ZmIFTqBluknBIk+vbIcIORE54ZPgFz5sCBkTOsGYAhODZ8gwGYYB2sWmQM4L7Ai-2itoEmCXIHRdOZnMceZBLlKQ4gAk7uz-AQrCpNXoQd7FFIL5buz36q4wxAECQWuM9eqbcZSqKkH37OMK8K-8cJ4nSebDnKFWL6uIn8nSAZnmpwg5YEDBAAEzd5JGzjoiW+j0Wa-Sbi3NuHdH40zxqdF+JMyYfy-jgH+cg-4AOpCAYB5tpKjAvAAL2lvoEiQQhpBQxlAShsVTbUCAA"&gt;playground&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Consuming the 3 types under one interface
&lt;/h3&gt;

&lt;p&gt;This utility allows us to easily and reliably consume the 3 types in a handler function. As I said in the beginning, if &lt;code&gt;t1&lt;/code&gt;, &lt;code&gt;t2&lt;/code&gt; and &lt;code&gt;t3&lt;/code&gt; are possible shapes for some config and we want a handler that should work for all the possible combinations, then we can write it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handlerFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// can access all of the keys, not only the common ones&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tval&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// cannot be undefined&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tval&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// can also be undefined&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tval&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// can also be undefined&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tval&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// can also be undefined&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tval&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// can also be undefined&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;when consuming the value, I get&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// hover in playground over each key to see the computed type&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nl"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nl"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;c&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nl"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nl"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;e&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;handlerFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tval1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;handlerFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tval2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;handlerFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tval3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  You might not need this
&lt;/h3&gt;

&lt;p&gt;This was a nice theoretical experiment. In practice, you might not need such manipulations. You might as well write the merged type by hand and move on with your life. But I see cases in which you will need this. For example, this might be part of a library that allows people to write their configs and the compiler would infer the shape of that config. Then, wherever that config is consumed, you will get a compiler error if a change causes issues, without having to update a type definition.&lt;/p&gt;

&lt;h3&gt;
  
  
  Only works for top-level keys
&lt;/h3&gt;

&lt;p&gt;This merging pattern only works for the top level keys. For the issue I had at hand, this was enough. But there might be cases in which the merging should go deeper. Typescript 4.1 introduced &lt;a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#recursive-conditional-types"&gt;recursive conditional types&lt;/a&gt; which could be used to extend this pattern.&lt;/p&gt;

&lt;p&gt;As an inspiration, I found &lt;a href="https://stackoverflow.com/a/60843132/3970447"&gt;this explanation&lt;/a&gt; on deep merging two types on stack overflow. I still haven't fully understood it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alternative for Merge
&lt;/h3&gt;

&lt;p&gt;A nice perk of the resulting type is that it respects the original constraints of the types when instantiating a new object. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tval1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1234&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// valid assignment&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tval1_1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// invalid assignment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could have written &lt;code&gt;Merge&lt;/code&gt; as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Merge2&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;AllKeys&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;PickType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the above invalid assignment would become valid if we were to specify the optionals as &lt;code&gt;undefined&lt;/code&gt; in the object creation, since we lose the information about what is required and what is optional.&lt;/p&gt;

&lt;p&gt;This conclusion is mostly theoretical, since this approach is the most useful when consuming the 3 types, not when creating an object of each of the types.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed reading this post and found it useful. Please leave a comment if you have any feedback and let me know if I have missed something or if you find a way to improve on top of this.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
