{"title":"Ond\u0159ej Syn\u00e1\u010dek | software engineer","link":[{"@attributes":{"rel":"self","type":"application\/atom+xml","href":"https:\/\/synacek.org\/atom.xml"}},{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org"}}],"generator":"Zola","updated":"2025-10-05T00:00:00+00:00","id":"https:\/\/synacek.org\/atom.xml","entry":[{"title":"heic-convert","published":"2025-10-05T00:00:00+00:00","updated":"2025-10-05T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/utilities\/heic-convert\/"}},"id":"https:\/\/synacek.org\/code\/utilities\/heic-convert\/","content":{"@attributes":{"type":"html"}}},{"title":"HTML Colors Raycast Extension","published":"2025-04-12T00:00:00+00:00","updated":"2025-04-12T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/projects\/html-colors\/"}},"id":"https:\/\/synacek.org\/code\/projects\/html-colors\/","content":"\n\n\n  \n\n\n<div class=\"centered video\">\n  \n  <figure>\n  \n    <video\n     controls=\"\"\n     muted=\"\"\n     title=\"UI interface\"\n     src=\"&#x2F;video&#x2F;html-colors.mp4\"\n     class=\"\"\n     width=\"600px\"\n     height=\"auto\"\n    >\n    <\/video>\n    \n      <figcaption>\n        Browsing the colors\n      <\/figcaption>\n    \n  \n  <\/figure>\n  \n<\/div>\n<p>The extension was created from my own need. I often need to create one-off projects with simple purpose but I still like to have some kind of color palette.<\/p>\n<p>Using basic web colors gives me much clearer sense when reading the source HTML code.<\/p>\n<div class=\"row wrap centered\">\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;html-colors-01.png\"\n        loading=\"lazy\"\n        alt=\"html-colors Raycast Extension Store pageExtension store page\"\n        width=\"300px\"\n        height=\"auto;\"\n      \/>\n      \n      \n        <figcaption>\n          Extension store page\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;html-colors-02.png\"\n        loading=\"lazy\"\n        alt=\"html-colors UI interface detail of colorExtension can display color metadata\"\n        width=\"300px\"\n        height=\"auto;\"\n      \/>\n      \n      \n        <figcaption>\n          Extension can display color metadata\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;html-colors-03.png\"\n        loading=\"lazy\"\n        alt=\"html-colors UI interface filtering viewFilter colors by basic or extended set, or group by their shade category\"\n        width=\"300px\"\n        height=\"auto;\"\n      \/>\n      \n      \n        <figcaption>\n          Filter colors by basic or extended set, or group by their shade category\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n<\/div>\n"},{"title":"There are a lot of ways to break up long tasks in JavaScript.","published":"2025-02-12T05:43:30+00:00","updated":"2025-02-12T05:43:30+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/there-are-a-lot-of-ways-to-break-up-long-tasks-in\/"}},"id":"https:\/\/synacek.org\/links\/there-are-a-lot-of-ways-to-break-up-long-tasks-in\/","content":{"@attributes":{"type":"html"}}},{"title":"How I configure my Git identities","published":"2024-12-30T15:11:58+00:00","updated":"2024-12-30T15:11:58+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/how-i-configure-my-git-identities\/"}},"id":"https:\/\/synacek.org\/links\/how-i-configure-my-git-identities\/","content":"<p>Configurations for git when you need to maintain multiple identities.<\/p>\n"},{"title":"Kitchen Soap \u2013 On Being A Senior Engineer","published":"2024-08-28T06:24:49+00:00","updated":"2024-08-28T06:24:49+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/kitchen-soap-on-being-a-senior-engineer\/"}},"id":"https:\/\/synacek.org\/links\/kitchen-soap-on-being-a-senior-engineer\/","content":{"@attributes":{"type":"html"}}},{"title":"An approach to optimizing TypeScript type checking performance | EdgeDB Blog","published":"2024-08-13T06:31:13+00:00","updated":"2024-08-13T06:31:13+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/an-approach-to-optimizing-typescript-type-checking\/"}},"id":"https:\/\/synacek.org\/links\/an-approach-to-optimizing-typescript-type-checking\/","content":{"@attributes":{"type":"html"}}},{"title":"Patterns for Memory Efficient DOM Manipulation with Modern Vanilla JavaScript \u2013 Frontend Masters Boost","published":"2024-07-30T07:00:07+00:00","updated":"2024-07-30T07:00:07+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/patterns-for-memory-efficient-dom-manipulation-wit\/"}},"id":"https:\/\/synacek.org\/links\/patterns-for-memory-efficient-dom-manipulation-wit\/","content":"<p>Some great tips for DOM manipulation with regular Javascript. The solution for removing multiple event listeners using  is especially useful.<\/p>\n"},{"title":"Why Patching Globals Is Harmful - kettanaito.com","published":"2024-05-11T11:53:33+00:00","updated":"2024-05-11T11:53:33+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/why-patching-globals-is-harmful-kettanaito-com\/"}},"id":"https:\/\/synacek.org\/links\/why-patching-globals-is-harmful-kettanaito-com\/","content":{"@attributes":{"type":"html"}}},{"title":"Understanding Relay Data IDs","published":"2024-04-07T21:56:18+00:00","updated":"2024-04-08T21:21:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/log\/understanding-relay-data-ids\/"}},"id":"https:\/\/synacek.org\/log\/understanding-relay-data-ids\/","content":"<p>When I started using the Relay framework, I wasn't really sure what I was looking at inside the dev tools.<\/p>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;understanding_relay_data_ids-01.png\"\n        loading=\"lazy\"\n        alt=\"Screenshot of Relay dev toolsRelay dev tools\"\n        width=\"600px\"\n        height=\"auto;\"\n      \/>\n      \n      \n        <figcaption>\n          Relay dev tools\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n<p>At first glance, it all may seem quite confusing, but after spending a few years working with Relay, it has started to make a lot more sense.<\/p>\n<p>Firstly, it's important to note that Relay's store<sup class=\"footnote-reference\"><a href=\"#1\">1<\/a><\/sup> employs a normalized cache. Picture the store as a key-value data structure, like a <code>Map<\/code>; this is what is contained within the Relay store. As a user of the library, this is mostly abstracted away from you.<\/p>\n<p>The <code>Data ID<\/code> is used as a <strong>key<\/strong> for a given value in the normalized cache. Consider the following type being a part of your schema:<\/p>\n<pre data-lang=\"graphql\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-graphql \"><code class=\"language-graphql\" data-lang=\"graphql\"><span style=\"color:#b48ead;\">type <\/span><span>User {\n<\/span><span>  id: <\/span><span style=\"color:#bf616a;\">ID<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>  name: <\/span><span style=\"color:#bf616a;\">String<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>  email: <\/span><span style=\"color:#bf616a;\">String<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>}\n<\/span><\/code><\/pre>\n<p>Whenever you query or receive a payload containing a type with an <code>id<\/code> field, that value will be used as a key for the normalized cache in the store.\nConsider an example type from above. Imagine your schema includes a query like this:<\/p>\n<pre data-lang=\"graphql\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-graphql \"><code class=\"language-graphql\" data-lang=\"graphql\"><span style=\"color:#b48ead;\">type <\/span><span>Query {\n<\/span><span>  getUser(<\/span><span style=\"color:#bf616a;\">id<\/span><span>: <\/span><span style=\"color:#bf616a;\">ID<\/span><span style=\"color:#b48ead;\">!<\/span><span>): <\/span><span style=\"color:#bf616a;\">User\n<\/span><span>}\n<\/span><\/code><\/pre>\n<p>Perhaps your Relay code is querying this data somewhere, and you are obtaining some fields using a fragment.<\/p>\n<pre data-lang=\"tsx\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-tsx \"><code class=\"language-tsx\" data-lang=\"tsx\"><span style=\"color:#65737e;\">\/\/ user-panel.tsx\n<\/span><span style=\"color:#b48ead;\">export function <\/span><span style=\"color:#8fa1b3;\">UserPanel<\/span><span>() {\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">const <\/span><span style=\"color:#bf616a;\">data <\/span><span>= <\/span><span style=\"color:#8fa1b3;\">useLazyLoadQuery<\/span><span>&lt;userPanelQuery&gt;(\n<\/span><span>        <\/span><span style=\"color:#8fa1b3;\">graphql<\/span><span>`\n<\/span><span style=\"color:#a3be8c;\">          query userPanelQuery($id: ID!) {\n<\/span><span style=\"color:#a3be8c;\">            getUser(id: $id) {\n<\/span><span style=\"color:#a3be8c;\">              ...userPanelHeading_User\n<\/span><span style=\"color:#a3be8c;\">            }\n<\/span><span style=\"color:#a3be8c;\">          }\n<\/span><span style=\"color:#a3be8c;\">        <\/span><span>`,\n<\/span><span>        {\n<\/span><span>          id: &quot;<\/span><span style=\"color:#a3be8c;\">MTpVc2VyOjEyMw==<\/span><span>&quot;,\n<\/span><span>        }\n<\/span><span>    );\n<\/span><span>\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">return <\/span><span>&lt;<\/span><span style=\"color:#ebcb8b;\">UserPanelHeading <\/span><span style=\"color:#d08770;\">dataRef<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">getUser<\/span><span style=\"color:#ab7967;\">} <\/span><span>\/&gt;;\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#65737e;\">\/\/ user-panel-heading.tsx\n<\/span><span style=\"color:#b48ead;\">export function <\/span><span style=\"color:#8fa1b3;\">UserPanelHeading<\/span><span>({ <\/span><span style=\"color:#bf616a;\">dataRef <\/span><span>}: { <\/span><span style=\"color:#bf616a;\">dataRef<\/span><span>: userPanelHeading_User$key }) {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">const <\/span><span>{ <\/span><span style=\"color:#bf616a;\">id<\/span><span>, <\/span><span style=\"color:#bf616a;\">name <\/span><span>} = <\/span><span style=\"color:#8fa1b3;\">useFragment<\/span><span>(<\/span><span style=\"color:#8fa1b3;\">graphql<\/span><span>`\n<\/span><span style=\"color:#a3be8c;\">    fragment userPanelHeading_User on User {\n<\/span><span style=\"color:#a3be8c;\">      __typename\n<\/span><span style=\"color:#a3be8c;\">      id\n<\/span><span style=\"color:#a3be8c;\">      name\n<\/span><span style=\"color:#a3be8c;\">    }\n<\/span><span style=\"color:#a3be8c;\">  <\/span><span>`, <\/span><span style=\"color:#bf616a;\">dataRef<\/span><span>)\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">return <\/span><span>&lt;<\/span><span style=\"color:#bf616a;\">p <\/span><span style=\"color:#d08770;\">key<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">id<\/span><span style=\"color:#ab7967;\">}<\/span><span>&gt;&lt;<\/span><span style=\"color:#bf616a;\">strong<\/span><span>&gt;Name: &lt;\/<\/span><span style=\"color:#bf616a;\">strong<\/span><span>&gt;<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">name<\/span><span style=\"color:#ab7967;\">}<\/span><span>&lt;\/<\/span><span style=\"color:#bf616a;\">p<\/span><span>&gt;\n<\/span><span>}\n<\/span><\/code><\/pre>\n<p>And the response might look something like this:<\/p>\n<pre data-lang=\"json\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-json \"><code class=\"language-json\" data-lang=\"json\"><span>{\n<\/span><span>  &quot;<\/span><span style=\"color:#a3be8c;\">data<\/span><span>&quot;: {\n<\/span><span>    &quot;<\/span><span style=\"color:#a3be8c;\">getUser<\/span><span>&quot;: {\n<\/span><span>      &quot;<\/span><span style=\"color:#a3be8c;\">__typename<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">User<\/span><span>&quot;,\n<\/span><span>      &quot;<\/span><span style=\"color:#a3be8c;\">id<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">MTpVc2VyOjEyMw==<\/span><span>&quot;,\n<\/span><span>      &quot;<\/span><span style=\"color:#a3be8c;\">name<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">Johnny Appleseed<\/span><span>&quot;\n<\/span><span>    }\n<\/span><span>  }\n<\/span><span>}\n<\/span><\/code><\/pre>\n<aside role=\"note\" class=\"callout\">\n  <div class=\"callout__icon\">\n    \ud83d\udca1\n  <\/div>\n  <div class=\"callout__content\">\n    \n    <h4 class=\"callout__content__title\">\n      __typename field\n    <\/h4>\n    \n    <div class=\"callout__content__body\">\n      <p>The field is part of the <code>userPanelHeading_User<\/code> fragment but it is not required, Relay store infers <code>__typename<\/code> automatically if your <code>ID<\/code> conforms to specification of encoded Global GraphQLIDs.<\/p>\n\n    <\/div>\n  <\/div>\n<\/aside>\n<p>Upon receiving data, Relay recognizes the <code>getUser<\/code> field to return the <code>User<\/code> type. It then uses the key <code>id<\/code> with corresponding value of <code>MTpVc2VyOjEyMw==<\/code> and registers this in the store. Relay dev tools will display a key labeled <code>getUser(id: \"MTpVc2VyOjEyMw==\")<\/code>.\nThis record, however, contains a <code>__ref<\/code> attribute that redirects to a separate stored record holding the <code>MTpVc2VyOjEyMw==<\/code> ID:<\/p>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;understanding_relay_data_ids-02.png\"\n        loading=\"lazy\"\n        alt=\"Screenshot of a field named getUser inside Relay dev toolsName of the query and its arguments used as a key\"\n        width=\"400px\"\n        height=\"auto;\"\n      \/>\n      \n      \n        <figcaption>\n          Name of the query and its arguments used as a key\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n<p>While the implications of this may seem negligible, its significance becomes apparent within a data-intensive application. Relay's approach emphasizes minimizing <em>waterfall<\/em> requests. This is evident in its adoption of data fragments, which are linked to React components. These fragments define data dependencies, however the actual data fetching can be triggered elsewhere within your component tree, optimally at the top, at the root level.<\/p>\n<p>Let's' adjust our schema so the <code>User<\/code> implements the Node interface<sup class=\"footnote-reference\"><a href=\"#2\">2<\/a><\/sup>. The Query now defines the <code>node<\/code> field:<\/p>\n<pre data-lang=\"graphql\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-graphql \"><code class=\"language-graphql\" data-lang=\"graphql\"><span style=\"color:#b48ead;\">interface <\/span><span>Node {\n<\/span><span>  id: <\/span><span style=\"color:#bf616a;\">ID<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">type <\/span><span>User <\/span><span style=\"color:#b48ead;\">implements <\/span><span style=\"color:#bf616a;\">Node <\/span><span>{\n<\/span><span>  id: <\/span><span style=\"color:#bf616a;\">ID<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>  name: <\/span><span style=\"color:#bf616a;\">String<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>  email: <\/span><span style=\"color:#bf616a;\">String<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">type <\/span><span>Query {\n<\/span><span>  getUser(<\/span><span style=\"color:#bf616a;\">id<\/span><span>: <\/span><span style=\"color:#bf616a;\">ID<\/span><span style=\"color:#b48ead;\">!<\/span><span>): <\/span><span style=\"color:#bf616a;\">User\n<\/span><span>  node(<\/span><span style=\"color:#bf616a;\">id<\/span><span>: <\/span><span style=\"color:#bf616a;\">ID<\/span><span style=\"color:#b48ead;\">!<\/span><span>): <\/span><span style=\"color:#bf616a;\">User\n<\/span><span>}\n<\/span><\/code><\/pre>\n<p>Consider the following scenario: you have a different UI, let's say a <code>Card<\/code> component which only needs user's email to render a gravatar. We'll use different fragment:<\/p>\n<pre data-lang=\"tsx\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-tsx \"><code class=\"language-tsx\" data-lang=\"tsx\"><span style=\"color:#65737e;\">\/\/ user-card.tsx\n<\/span><span style=\"color:#b48ead;\">export function <\/span><span style=\"color:#8fa1b3;\">UserCard<\/span><span>() {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">const <\/span><span style=\"color:#bf616a;\">data <\/span><span>= <\/span><span style=\"color:#8fa1b3;\">useLazyLoadQuery<\/span><span>&lt;userCardQuery&gt;(\n<\/span><span>    <\/span><span style=\"color:#8fa1b3;\">graphql<\/span><span>`\n<\/span><span style=\"color:#a3be8c;\">      query userCardQuery($id: ID!) {\n<\/span><span style=\"color:#a3be8c;\">        node(id: $id) {\n<\/span><span style=\"color:#a3be8c;\">          __typename\n<\/span><span style=\"color:#a3be8c;\">          ... on User {\n<\/span><span style=\"color:#a3be8c;\">            ...userAvatar_User\n<\/span><span style=\"color:#a3be8c;\">          }\n<\/span><span style=\"color:#a3be8c;\">        }\n<\/span><span style=\"color:#a3be8c;\">      }\n<\/span><span style=\"color:#a3be8c;\">    <\/span><span>`,\n<\/span><span>    {\n<\/span><span>      id: &quot;<\/span><span style=\"color:#a3be8c;\">MTpVc2VyOjEyMw==<\/span><span>&quot;,\n<\/span><span>    }\n<\/span><span>  );\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">if <\/span><span>(!<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">node<\/span><span>) {\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">return <\/span><span style=\"color:#d08770;\">null<\/span><span>;\n<\/span><span>  }\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">if <\/span><span>(<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">node<\/span><span>.<\/span><span style=\"color:#bf616a;\">__typename <\/span><span>!== &quot;<\/span><span style=\"color:#a3be8c;\">User<\/span><span>&quot;) {\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">return <\/span><span style=\"color:#d08770;\">null<\/span><span>;\n<\/span><span>  }\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">return <\/span><span>&lt;<\/span><span style=\"color:#ebcb8b;\">UserAvatar <\/span><span style=\"color:#d08770;\">dataRef<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">node<\/span><span style=\"color:#ab7967;\">} <\/span><span>\/&gt;;\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#65737e;\">\/\/ user-avatar.tsx\n<\/span><span style=\"color:#b48ead;\">export function <\/span><span style=\"color:#8fa1b3;\">UserAvatar<\/span><span>({ <\/span><span style=\"color:#bf616a;\">dataRef <\/span><span>}: { <\/span><span style=\"color:#bf616a;\">dataRef<\/span><span>: userAvatar_User$key }) {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">const <\/span><span>{ <\/span><span style=\"color:#bf616a;\">email <\/span><span>} = <\/span><span style=\"color:#8fa1b3;\">useFragment<\/span><span>(<\/span><span style=\"color:#8fa1b3;\">graphql<\/span><span>`\n<\/span><span style=\"color:#a3be8c;\">    fragment userAvatar_User on User {\n<\/span><span style=\"color:#a3be8c;\">      __typename\n<\/span><span style=\"color:#a3be8c;\">      id\n<\/span><span style=\"color:#a3be8c;\">      email\n<\/span><span style=\"color:#a3be8c;\">    }\n<\/span><span style=\"color:#a3be8c;\">  <\/span><span>`, <\/span><span style=\"color:#bf616a;\">dataRef<\/span><span>);\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">return <\/span><span>&lt;<\/span><span style=\"color:#ebcb8b;\">Gravatar <\/span><span style=\"color:#d08770;\">email<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">email<\/span><span style=\"color:#ab7967;\">} <\/span><span>\/&gt;;\n<\/span><span>}\n<\/span><\/code><\/pre>\n<p>Calling the <code>node<\/code> query, we receive the following payload:<\/p>\n<pre data-lang=\"json\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-json \"><code class=\"language-json\" data-lang=\"json\"><span>{\n<\/span><span>  &quot;<\/span><span style=\"color:#a3be8c;\">data<\/span><span>&quot;: {\n<\/span><span>    &quot;<\/span><span style=\"color:#a3be8c;\">node<\/span><span>&quot;: {\n<\/span><span>      &quot;<\/span><span style=\"color:#a3be8c;\">__typename<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">User<\/span><span>&quot;,\n<\/span><span>      &quot;<\/span><span style=\"color:#a3be8c;\">id<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">MTpVc2VyOjEyMw==<\/span><span>&quot;,\n<\/span><span>      &quot;<\/span><span style=\"color:#a3be8c;\">email<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">johnny@apple.com<\/span><span>&quot;\n<\/span><span>    }\n<\/span><span>  }\n<\/span><span>}\n<\/span><\/code><\/pre>\n<p>Now the real fun part begins. Instead of storing this data under a new key, Relay recognizes that you've requested the same user data, but this time with additional fields. It retains the existing data but enhances it with the <code>email<\/code> field. Brilliant!<\/p>\n<p>You can verify this by revisiting the Relay dev tools. You will now see a new key next to the existing <code>getUser<\/code> key, holding the value: <code>node(id: \"MTpVc2VyOjEyMw==\")<\/code>. However, it also has a <em>reference<\/em> (note the <code>__ref<\/code> property), which leads back to the same <code>User<\/code> record which now also includes the requested <code>email<\/code> field:<\/p>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;understanding_relay_data_ids-03.png\"\n        loading=\"lazy\"\n        alt=\"Screenshot of a Relay dev tools displaying node and getUser fieldsBoth `node` and `getUser` reference the same `User` object\"\n        width=\"400px\"\n        height=\"auto;\"\n      \/>\n      \n      \n        <figcaption>\n          Both `node` and `getUser` reference the same `User` object\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n<p>This significantly saves space in large applications, with the benefits becoming even more apparent when managing connections.<\/p>\n<p>It's worth mentioning that it's quite common for some queries not to return a type with <code>id<\/code>. Let's introduce <code>Version<\/code> type, which characterizes a version of our application. This type doesn't include <code>id<\/code>, so it's unreachable via the <code>node<\/code> query.<\/p>\n<pre data-lang=\"graphql\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-graphql \"><code class=\"language-graphql\" data-lang=\"graphql\"><span style=\"color:#b48ead;\">type <\/span><span>Version {\n<\/span><span>  value: <\/span><span style=\"color:#bf616a;\">String<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">type <\/span><span>Query {\n<\/span><span>  getUser(<\/span><span style=\"color:#bf616a;\">id<\/span><span>: <\/span><span style=\"color:#bf616a;\">ID<\/span><span style=\"color:#b48ead;\">!<\/span><span>): <\/span><span style=\"color:#bf616a;\">User\n<\/span><span>  node(<\/span><span style=\"color:#bf616a;\">id<\/span><span>: <\/span><span style=\"color:#bf616a;\">ID<\/span><span style=\"color:#b48ead;\">!<\/span><span>): <\/span><span style=\"color:#bf616a;\">User\n<\/span><span>  version: <\/span><span style=\"color:#bf616a;\">Version\n<\/span><span>}\n<\/span><\/code><\/pre>\n<p>We will query for that field via React component:<\/p>\n<pre data-lang=\"tsx\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-tsx \"><code class=\"language-tsx\" data-lang=\"tsx\"><span style=\"color:#65737e;\">\/\/ version.tsx\n<\/span><span style=\"color:#b48ead;\">export function <\/span><span style=\"color:#8fa1b3;\">Version<\/span><span>() {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">const <\/span><span style=\"color:#bf616a;\">data <\/span><span>= <\/span><span style=\"color:#8fa1b3;\">useLazyLoadQuery<\/span><span>&lt;versionQuery&gt;(\n<\/span><span>    <\/span><span style=\"color:#8fa1b3;\">graphql<\/span><span>`\n<\/span><span style=\"color:#a3be8c;\">      query versionQuery {\n<\/span><span style=\"color:#a3be8c;\">        version {\n<\/span><span style=\"color:#a3be8c;\">          __typename\n<\/span><span style=\"color:#a3be8c;\">          value\n<\/span><span style=\"color:#a3be8c;\">        }\n<\/span><span style=\"color:#a3be8c;\">      }\n<\/span><span style=\"color:#a3be8c;\">    <\/span><span>`,\n<\/span><span>    {}\n<\/span><span>  );\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">return <\/span><span>&lt;<\/span><span style=\"color:#bf616a;\">pre<\/span><span>&gt;<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">data<\/span><span>.version?.value<\/span><span style=\"color:#ab7967;\">}<\/span><span>&lt;\/<\/span><span style=\"color:#bf616a;\">pre<\/span><span>&gt;\n<\/span><span>}\n<\/span><\/code><\/pre>\n<p>Upon calling <code>version<\/code> query, the returned payload from server has this shape:<\/p>\n<pre data-lang=\"json\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-json \"><code class=\"language-json\" data-lang=\"json\"><span>{\n<\/span><span>  &quot;<\/span><span style=\"color:#a3be8c;\">data<\/span><span>&quot;: {\n<\/span><span>    &quot;<\/span><span style=\"color:#a3be8c;\">version<\/span><span>&quot;: {\n<\/span><span>      &quot;<\/span><span style=\"color:#a3be8c;\">__typename<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">Version<\/span><span>&quot;,\n<\/span><span>      &quot;<\/span><span style=\"color:#a3be8c;\">value<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">1.0.0<\/span><span>&quot;\n<\/span><span>    }\n<\/span><span>  }\n<\/span><span>}\n<\/span><\/code><\/pre>\n<p>Inspecting the store via dev tools reveals that store now holds this data under key <code>version<\/code>, it points to this data via <code>__ref<\/code> property which has value <code>client:root:version<\/code>. How come?<\/p>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;understanding_relay_data_ids-04.png\"\n        loading=\"lazy\"\n        alt=\"Screenshot of a Relay dev tools displaying version fieldNotice anything strange?\"\n        width=\"400px\"\n        height=\"auto;\"\n      \/>\n      \n      \n        <figcaption>\n          Notice anything strange?\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n<p>Since this query and its returned type do not contain an <code>id<\/code> field, Relay requires a unique <em>key<\/em> to store it in its normalized cache. Typically, this would be the value of the <code>id<\/code>. However, in this absence, the library generates its own unique key instead.<\/p>\n<p>It's using <code>client:root<\/code> as its prefix. This prefix is pre-defined in the library, then it appends name of the query <code>version<\/code> which results in <code>client:root:version<\/code>.<\/p>\n<p>Every piece of data can be referenced in the cache by a key. This key is known as <strong>Data ID<\/strong>. <strong>For types that contain an <code>id<\/code> field, they resolve to its value. For types that lack an <code>id<\/code> field, Relay generates one for you<\/strong>. This applies to our <code>Version<\/code> type as returned by our <code>version<\/code> query.<\/p>\n<p>Relay can expose this Data ID value in queries using a special field <code>__id<\/code>. I call it special because this field, although not part of the schema, is added by Relay to every type for ease of use. This allows us to broaden our <code>node<\/code> query to retrieve the Data ID for the <code>User<\/code> type. In this case, the <code>id<\/code> value is what you will receive.<\/p>\n<pre data-lang=\"tsx\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-tsx \"><code class=\"language-tsx\" data-lang=\"tsx\"><span style=\"color:#65737e;\">\/\/ user-card.tsx\n<\/span><span style=\"color:#b48ead;\">export function <\/span><span style=\"color:#8fa1b3;\">UserCard<\/span><span>() {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">const <\/span><span style=\"color:#bf616a;\">data <\/span><span>= <\/span><span style=\"color:#8fa1b3;\">useLazyLoadQuery<\/span><span>&lt;userCardQuery&gt;(\n<\/span><span>    <\/span><span style=\"color:#8fa1b3;\">graphql<\/span><span>`\n<\/span><span style=\"color:#a3be8c;\">      query userCardQuery($id: ID!) {\n<\/span><span style=\"color:#a3be8c;\">        node(id: $id) {\n<\/span><span style=\"color:#a3be8c;\">          __typename\n<\/span><span style=\"color:#a3be8c;\">          ... on User {\n<\/span><span style=\"color:#a3be8c;\">            __id # &lt;&lt; we added special Data ID field\n<\/span><span style=\"color:#a3be8c;\">            ...userAvatar_User\n<\/span><span style=\"color:#a3be8c;\">          }\n<\/span><span style=\"color:#a3be8c;\">        }\n<\/span><span style=\"color:#a3be8c;\">      }\n<\/span><span style=\"color:#a3be8c;\">    <\/span><span>`,\n<\/span><span>    {\n<\/span><span>      id: &quot;<\/span><span style=\"color:#a3be8c;\">MTpVc2VyOjEyMw==<\/span><span>&quot;,\n<\/span><span>    }\n<\/span><span>  );\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">if <\/span><span>(!<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">node<\/span><span>) {\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">return <\/span><span style=\"color:#d08770;\">null<\/span><span>;\n<\/span><span>  }\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">if <\/span><span>(<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">node<\/span><span>.<\/span><span style=\"color:#bf616a;\">__typename <\/span><span>!== &quot;<\/span><span style=\"color:#a3be8c;\">User<\/span><span>&quot;) {\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">return <\/span><span style=\"color:#d08770;\">null<\/span><span>;\n<\/span><span>  }\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">return <\/span><span>(\n<\/span><span>    &lt;&gt;\n<\/span><span>      &lt;<\/span><span style=\"color:#bf616a;\">pre<\/span><span>&gt;<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">node<\/span><span>.<\/span><span style=\"color:#bf616a;\">__id<\/span><span style=\"color:#ab7967;\">}<\/span><span>&lt;\/<\/span><span style=\"color:#bf616a;\">pre<\/span><span>&gt;\n<\/span><span>      &lt;<\/span><span style=\"color:#ebcb8b;\">UserAvatar <\/span><span style=\"color:#d08770;\">dataRef<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">node<\/span><span style=\"color:#ab7967;\">} <\/span><span>\/&gt;\n<\/span><span>    &lt;\/&gt;\n<\/span><span>  );\n<\/span><span>}\n<\/span><\/code><\/pre>\n<p>This will render <code>MTpVc2VyOjEyMw==<\/code> value inside <code>&lt;pre&gt;<\/code> tag. Does not look very useful at first sight but let's show an example with <code>version<\/code> query for completeness sake:<\/p>\n<pre data-lang=\"tsx\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-tsx \"><code class=\"language-tsx\" data-lang=\"tsx\"><span style=\"color:#65737e;\">\/\/ version.tsx\n<\/span><span style=\"color:#b48ead;\">export function <\/span><span style=\"color:#8fa1b3;\">Version<\/span><span>() {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">const <\/span><span style=\"color:#bf616a;\">data <\/span><span>= <\/span><span style=\"color:#8fa1b3;\">useLazyLoadQuery<\/span><span>&lt;versionQuery&gt;(\n<\/span><span>    <\/span><span style=\"color:#8fa1b3;\">graphql<\/span><span>`\n<\/span><span style=\"color:#a3be8c;\">      query versionQuery {\n<\/span><span style=\"color:#a3be8c;\">        version {\n<\/span><span style=\"color:#a3be8c;\">          __id # &lt;&lt; Data ID field\n<\/span><span style=\"color:#a3be8c;\">          value\n<\/span><span style=\"color:#a3be8c;\">        }\n<\/span><span style=\"color:#a3be8c;\">      }\n<\/span><span style=\"color:#a3be8c;\">    <\/span><span>`,\n<\/span><span>    {}\n<\/span><span>  );\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">return <\/span><span>(\n<\/span><span>    &lt;&gt;\n<\/span><span>      &lt;<\/span><span style=\"color:#bf616a;\">pre<\/span><span>&gt;<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">data<\/span><span>.version?.<\/span><span style=\"color:#bf616a;\">__id<\/span><span style=\"color:#ab7967;\">}<\/span><span>&lt;\/<\/span><span style=\"color:#bf616a;\">pre<\/span><span>&gt;\n<\/span><span>      &lt;<\/span><span style=\"color:#bf616a;\">pre<\/span><span>&gt;<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">data<\/span><span>.version?.value<\/span><span style=\"color:#ab7967;\">}<\/span><span>&lt;\/<\/span><span style=\"color:#bf616a;\">pre<\/span><span>&gt;\n<\/span><span>    &lt;\/&gt;\n<\/span><span>  );\n<\/span><span>}\n<\/span><\/code><\/pre>\n<p>This code renders <code>client:root:version<\/code> inside <code>&lt;pre&gt;{data.version?.__id}&lt;\/pre&gt;<\/code> tag, which is located right above the <code>1.0.0<\/code> string (which is our value for the <code>value<\/code> field).<\/p>\n<p>The usefulness of <code>__id<\/code> becomes increasingly apparent when your application performs advanced data manipulation within the Relay store. These manipulations often take place inside <em>updater<\/em> functions<sup class=\"footnote-reference\"><a href=\"#3\">3<\/a><\/sup>, with an imperative approach.<\/p>\n<p>It's typically straightforward to pass the <code>id<\/code> field to a mutation, but the <code>__id<\/code> field becomes far more crucial when dealing with connections. I will illustrate this concept by showing you how Data IDs are represented in connections.<\/p>\n<p>We will enhance the schema and modify our <code>User<\/code> type. This type will now include a <code>friends<\/code> field that will return a new type, <code>UserConnection<\/code>. This field conforms to the Relay pagination specification, so we're also adding other types that should be included:<\/p>\n<p><em>Note: I'm omitting some types we declared in previous steps for brevity.<\/em><\/p>\n<pre data-lang=\"graphql\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-graphql \"><code class=\"language-graphql\" data-lang=\"graphql\"><span style=\"color:#65737e;\"># ...\n<\/span><span style=\"color:#b48ead;\">type <\/span><span>PageInfo {\n<\/span><span>  hasNextPage: <\/span><span style=\"color:#bf616a;\">Boolean<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>  hasPreviousPage: <\/span><span style=\"color:#bf616a;\">Boolean<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>  endCursor: <\/span><span style=\"color:#bf616a;\">String\n<\/span><span>  startCursor: <\/span><span style=\"color:#bf616a;\">String\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">type <\/span><span>UserEdge {\n<\/span><span>  cursor: <\/span><span style=\"color:#bf616a;\">String<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>  node: <\/span><span style=\"color:#bf616a;\">User\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">type <\/span><span>UserConnection {\n<\/span><span>  edges: <\/span><span style=\"color:#b48ead;\">[<\/span><span style=\"color:#bf616a;\">UserEdge<\/span><span style=\"color:#b48ead;\">]\n<\/span><span>  pageInfo: <\/span><span style=\"color:#bf616a;\">PageInfo<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">input <\/span><span>FriendSearchInput {\n<\/span><span>  <\/span><span style=\"color:#bf616a;\">name<\/span><span>: <\/span><span style=\"color:#bf616a;\">String\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">type <\/span><span>User {\n<\/span><span>  id: <\/span><span style=\"color:#bf616a;\">ID<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>  name: <\/span><span style=\"color:#bf616a;\">String<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>  email: <\/span><span style=\"color:#bf616a;\">String<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>  friends(<\/span><span style=\"color:#bf616a;\">first<\/span><span>: <\/span><span style=\"color:#bf616a;\">Int<\/span><span style=\"color:#b48ead;\">!<\/span><span>, <\/span><span style=\"color:#bf616a;\">after<\/span><span>: <\/span><span style=\"color:#bf616a;\">String<\/span><span>, <\/span><span style=\"color:#bf616a;\">search<\/span><span>: <\/span><span style=\"color:#bf616a;\">FriendSearchInput<\/span><span>): <\/span><span style=\"color:#bf616a;\">UserConnection<\/span><span style=\"color:#b48ead;\">!\n<\/span><span>}\n<\/span><\/code><\/pre>\n<p>You can think of the <code>friends<\/code> field as a list of friends the given user has, with ability to filter this list with <code>search<\/code> argument. Let's augment <code>UserCard<\/code> component <code>node<\/code> query with new fragment. This fragment will belong to new component <code>FriendList<\/code>:<\/p>\n<pre data-lang=\"tsx\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-tsx \"><code class=\"language-tsx\" data-lang=\"tsx\"><span style=\"color:#65737e;\">\/\/ user-card.tsx\n<\/span><span style=\"color:#b48ead;\">export function <\/span><span style=\"color:#8fa1b3;\">UserCard<\/span><span>() {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">const <\/span><span>[<\/span><span style=\"color:#bf616a;\">search<\/span><span>, <\/span><span style=\"color:#bf616a;\">setSearch<\/span><span>] = <\/span><span style=\"color:#8fa1b3;\">useState<\/span><span>(&quot;&quot;);\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">const <\/span><span>[<\/span><span style=\"color:#bf616a;\">debouncedSearch<\/span><span>] = <\/span><span style=\"color:#8fa1b3;\">useDebounce<\/span><span>(<\/span><span style=\"color:#bf616a;\">search<\/span><span>, <\/span><span style=\"color:#d08770;\">500<\/span><span>);\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">const <\/span><span style=\"color:#bf616a;\">data <\/span><span>= <\/span><span style=\"color:#8fa1b3;\">useLazyLoadQuery<\/span><span>&lt;userCardQuery&gt;(\n<\/span><span>    <\/span><span style=\"color:#8fa1b3;\">graphql<\/span><span>`\n<\/span><span style=\"color:#a3be8c;\">      query userCardQuery($id: ID!, $name: String!) {\n<\/span><span style=\"color:#a3be8c;\">        node(id: $id) {\n<\/span><span style=\"color:#a3be8c;\">          __typename\n<\/span><span style=\"color:#a3be8c;\">          ... on User {\n<\/span><span style=\"color:#a3be8c;\">            __id\n<\/span><span style=\"color:#a3be8c;\">            ...userAvatar_User\n<\/span><span style=\"color:#a3be8c;\">            ...friendList_User @arguments(name: $name, first: 10) # &lt;&lt; new field\n<\/span><span style=\"color:#a3be8c;\">          }\n<\/span><span style=\"color:#a3be8c;\">        }\n<\/span><span style=\"color:#a3be8c;\">      }\n<\/span><span style=\"color:#a3be8c;\">    <\/span><span>`,\n<\/span><span>    {\n<\/span><span>      id: &quot;<\/span><span style=\"color:#a3be8c;\">MTpVc2VyOjEyMw==<\/span><span>&quot;,\n<\/span><span>      name: <\/span><span style=\"color:#bf616a;\">debouncedSearch<\/span><span>.<\/span><span style=\"color:#8fa1b3;\">trim<\/span><span>().length &gt; <\/span><span style=\"color:#d08770;\">0 <\/span><span>? <\/span><span style=\"color:#bf616a;\">debouncedSearch <\/span><span>: &quot;&quot;,\n<\/span><span>    }\n<\/span><span>  );\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">const <\/span><span style=\"color:#8fa1b3;\">handleInputChange <\/span><span>= (<\/span><span style=\"color:#bf616a;\">event<\/span><span>: React.ChangeEvent&lt;HTMLInputElement&gt;) <\/span><span style=\"color:#b48ead;\">=&gt; <\/span><span>{\n<\/span><span>    <\/span><span style=\"color:#8fa1b3;\">setSearch<\/span><span>(event.target.value);\n<\/span><span>  };\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">if <\/span><span>(!<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">node<\/span><span>) {\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">return <\/span><span style=\"color:#d08770;\">null<\/span><span>;\n<\/span><span>  }\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">if <\/span><span>(<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">node<\/span><span>.<\/span><span style=\"color:#bf616a;\">__typename <\/span><span>!== &quot;<\/span><span style=\"color:#a3be8c;\">User<\/span><span>&quot;) {\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">return <\/span><span style=\"color:#d08770;\">null<\/span><span>;\n<\/span><span>  }\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">return <\/span><span>(\n<\/span><span>    &lt;&gt;\n<\/span><span>      <\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#65737e;\">\/* Used for debugging purposes, shows Data ID of User type *\/<\/span><span style=\"color:#ab7967;\">}\n<\/span><span>      &lt;<\/span><span style=\"color:#bf616a;\">pre<\/span><span>&gt;<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">node<\/span><span>.<\/span><span style=\"color:#bf616a;\">__id<\/span><span style=\"color:#ab7967;\">}<\/span><span>&lt;\/<\/span><span style=\"color:#bf616a;\">pre<\/span><span>&gt;\n<\/span><span>      &lt;<\/span><span style=\"color:#ebcb8b;\">UserAvatar <\/span><span style=\"color:#d08770;\">dataRef<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">node<\/span><span style=\"color:#ab7967;\">} <\/span><span>\/&gt;\n<\/span><span>      &lt;<\/span><span style=\"color:#bf616a;\">label<\/span><span>&gt;\n<\/span><span>        Search:\n<\/span><span>        &lt;<\/span><span style=\"color:#bf616a;\">input <\/span><span style=\"color:#d08770;\">type<\/span><span>=&quot;<\/span><span style=\"color:#a3be8c;\">text<\/span><span>&quot; <\/span><span style=\"color:#d08770;\">value<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">search<\/span><span style=\"color:#ab7967;\">} <\/span><span style=\"color:#d08770;\">onChange<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">handleInputChange<\/span><span style=\"color:#ab7967;\">} <\/span><span>\/&gt;\n<\/span><span>      &lt;\/<\/span><span style=\"color:#bf616a;\">label<\/span><span>&gt;\n<\/span><span>      &lt;<\/span><span style=\"color:#ebcb8b;\">FriendList <\/span><span style=\"color:#d08770;\">dataRef<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">node<\/span><span style=\"color:#ab7967;\">} <\/span><span>\/&gt;\n<\/span><span>    &lt;\/&gt;\n<\/span><span>  );\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#65737e;\">\/\/ friend-list.tsx\n<\/span><span style=\"color:#b48ead;\">export function <\/span><span style=\"color:#8fa1b3;\">FriendList<\/span><span>({ <\/span><span style=\"color:#bf616a;\">dataRef <\/span><span>}: { <\/span><span style=\"color:#bf616a;\">dataRef<\/span><span>: friendList_User$key }) {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">const <\/span><span>{ <\/span><span style=\"color:#bf616a;\">data<\/span><span>, <\/span><span style=\"color:#bf616a;\">loadNext<\/span><span>, <\/span><span style=\"color:#bf616a;\">hasNext <\/span><span>} = <\/span><span style=\"color:#8fa1b3;\">usePaginationFragment<\/span><span>(\n<\/span><span>    <\/span><span style=\"color:#8fa1b3;\">graphql<\/span><span>`\n<\/span><span style=\"color:#a3be8c;\">      fragment friendList_User on User\n<\/span><span style=\"color:#a3be8c;\">      @refetchable(queryName: &quot;friendListPaginationQuery&quot;)\n<\/span><span style=\"color:#a3be8c;\">      @argumentDefinitions(\n<\/span><span style=\"color:#a3be8c;\">        first: { type: &quot;Int&quot;, defaultValue: 10 }\n<\/span><span style=\"color:#a3be8c;\">        cursor: { type: &quot;String&quot; }\n<\/span><span style=\"color:#a3be8c;\">        name: { type: &quot;String&quot;, defaultValue: &quot;&quot; }\n<\/span><span style=\"color:#a3be8c;\">      ) {\n<\/span><span style=\"color:#a3be8c;\">        friends(first: $first, after: $cursor, search: { name: $name })\n<\/span><span style=\"color:#a3be8c;\">          @connection(key: &quot;user_friends&quot;, filters: [&quot;search&quot;]) {\n<\/span><span style=\"color:#a3be8c;\">          __id # &lt;&lt; adding for debugging purposes\n<\/span><span style=\"color:#a3be8c;\">          edges {\n<\/span><span style=\"color:#a3be8c;\">            node {\n<\/span><span style=\"color:#a3be8c;\">              id\n<\/span><span style=\"color:#a3be8c;\">              name\n<\/span><span style=\"color:#a3be8c;\">            }\n<\/span><span style=\"color:#a3be8c;\">          }\n<\/span><span style=\"color:#a3be8c;\">        }\n<\/span><span style=\"color:#a3be8c;\">      }\n<\/span><span style=\"color:#a3be8c;\">    <\/span><span>`,\n<\/span><span>    <\/span><span style=\"color:#bf616a;\">dataRef<\/span><span>,\n<\/span><span>  );\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">if <\/span><span>(!<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">friends <\/span><span>|| !<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">friends<\/span><span>.<\/span><span style=\"color:#bf616a;\">edges<\/span><span>) {\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">return <\/span><span style=\"color:#d08770;\">null<\/span><span>;\n<\/span><span>  }\n<\/span><span>\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">return <\/span><span>(\n<\/span><span>    &lt;<\/span><span style=\"color:#bf616a;\">div<\/span><span>&gt;\n<\/span><span>      <\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#65737e;\">\/* Used for debugging purposes - shows Data ID for the connection itself, I refer to this ID in following paragraphs *\/<\/span><span style=\"color:#ab7967;\">}\n<\/span><span>      &lt;<\/span><span style=\"color:#bf616a;\">pre<\/span><span>&gt;<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">friends<\/span><span>.<\/span><span style=\"color:#bf616a;\">__id<\/span><span style=\"color:#ab7967;\">}<\/span><span>&lt;\/<\/span><span style=\"color:#bf616a;\">pre<\/span><span>&gt;\n<\/span><span>      &lt;<\/span><span style=\"color:#bf616a;\">ul<\/span><span>&gt;\n<\/span><span>        <\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">friends<\/span><span>.<\/span><span style=\"color:#bf616a;\">edges<\/span><span>.<\/span><span style=\"color:#8fa1b3;\">map<\/span><span>((<\/span><span style=\"color:#bf616a;\">user<\/span><span>) <\/span><span style=\"color:#b48ead;\">=&gt; <\/span><span>(\n<\/span><span>          &lt;<\/span><span style=\"color:#bf616a;\">li <\/span><span style=\"color:#d08770;\">key<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">user<\/span><span>?.<\/span><span style=\"color:#bf616a;\">node<\/span><span>?.id<\/span><span style=\"color:#ab7967;\">}<\/span><span>&gt;<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">user<\/span><span>?.<\/span><span style=\"color:#bf616a;\">node<\/span><span>?.name<\/span><span style=\"color:#ab7967;\">}<\/span><span>&lt;\/<\/span><span style=\"color:#bf616a;\">li<\/span><span>&gt;\n<\/span><span>        ))<\/span><span style=\"color:#ab7967;\">}\n<\/span><span>      &lt;\/<\/span><span style=\"color:#bf616a;\">ul<\/span><span>&gt;\n<\/span><span>      &lt;<\/span><span style=\"color:#bf616a;\">button <\/span><span style=\"color:#d08770;\">onClick<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span>() <\/span><span style=\"color:#b48ead;\">=&gt; <\/span><span style=\"color:#8fa1b3;\">loadNext<\/span><span>(<\/span><span style=\"color:#d08770;\">10<\/span><span>)<\/span><span style=\"color:#ab7967;\">} <\/span><span style=\"color:#d08770;\">disabled<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span>!<\/span><span style=\"color:#bf616a;\">hasNext<\/span><span style=\"color:#ab7967;\">}<\/span><span>&gt;\n<\/span><span>        Next\n<\/span><span>      &lt;\/<\/span><span style=\"color:#bf616a;\">button<\/span><span>&gt;\n<\/span><span>    &lt;\/<\/span><span style=\"color:#bf616a;\">div<\/span><span>&gt;\n<\/span><span>  );\n<\/span><span>}\n<\/span><\/code><\/pre>\n<p>The first page received from server has this shape:<\/p>\n<p><em>Note: I'm only including partial output to include first two edges.<\/em><\/p>\n<pre data-lang=\"json\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-json \"><code class=\"language-json\" data-lang=\"json\"><span>{\n<\/span><span>    &quot;<\/span><span style=\"color:#a3be8c;\">data<\/span><span>&quot;: {\n<\/span><span>        &quot;<\/span><span style=\"color:#a3be8c;\">node<\/span><span>&quot;: {\n<\/span><span>            &quot;<\/span><span style=\"color:#a3be8c;\">__typename<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">User<\/span><span>&quot;,\n<\/span><span>            &quot;<\/span><span style=\"color:#a3be8c;\">id<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">MTpVc2VyOjEyMw==<\/span><span>&quot;,\n<\/span><span>            &quot;<\/span><span style=\"color:#a3be8c;\">email<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">johnny@apple.com<\/span><span>&quot;,\n<\/span><span>            &quot;<\/span><span style=\"color:#a3be8c;\">friends<\/span><span>&quot;: {\n<\/span><span>                &quot;<\/span><span style=\"color:#a3be8c;\">edges<\/span><span>&quot;: [\n<\/span><span>                    {\n<\/span><span>                        &quot;<\/span><span style=\"color:#a3be8c;\">node<\/span><span>&quot;: {\n<\/span><span>                            &quot;<\/span><span style=\"color:#a3be8c;\">id<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">MTpVc2VyOmZhZDkxZjIxLTVhYzEtNDY3ZC1hOWU3LTljMmQwOTQzYjY5Zg==<\/span><span>&quot;,\n<\/span><span>                            &quot;<\/span><span style=\"color:#a3be8c;\">name<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">Lana Maggio<\/span><span>&quot;,\n<\/span><span>                            &quot;<\/span><span style=\"color:#a3be8c;\">__typename<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">User<\/span><span>&quot;\n<\/span><span>                        },\n<\/span><span>                        &quot;<\/span><span style=\"color:#a3be8c;\">cursor<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">MTpVc2VyOmZhZDkxZjIxLTVhYzEtNDY3ZC1hOWU3LTljMmQwOTQzYjY5Zg==<\/span><span>&quot;\n<\/span><span>                    },\n<\/span><span>                    {\n<\/span><span>                        &quot;<\/span><span style=\"color:#a3be8c;\">node<\/span><span>&quot;: {\n<\/span><span>                            &quot;<\/span><span style=\"color:#a3be8c;\">id<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">MTpVc2VyOjU2NDY2NWRhLWViNmYtNGM3My04MDQwLTk4MjUyNzU4ZDBjYw==<\/span><span>&quot;,\n<\/span><span>                            &quot;<\/span><span style=\"color:#a3be8c;\">name<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">Gertrude Rogahn<\/span><span>&quot;,\n<\/span><span>                            &quot;<\/span><span style=\"color:#a3be8c;\">__typename<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">User<\/span><span>&quot;\n<\/span><span>                        },\n<\/span><span>                        &quot;<\/span><span style=\"color:#a3be8c;\">cursor<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">MTpVc2VyOjU2NDY2NWRhLWViNmYtNGM3My04MDQwLTk4MjUyNzU4ZDBjYw==<\/span><span>&quot;\n<\/span><span>                    },\n<\/span><span>\n<\/span><span>                  <\/span><span style=\"color:#65737e;\">\/\/ 8 other edges ...\n<\/span><span>                ],\n<\/span><span>                &quot;<\/span><span style=\"color:#a3be8c;\">pageInfo<\/span><span>&quot;: {\n<\/span><span>                    &quot;<\/span><span style=\"color:#a3be8c;\">endCursor<\/span><span>&quot;: &quot;<\/span><span style=\"color:#a3be8c;\">MTpVc2VyOmMwYmIzYjY0LTJlZDgtNGRjNS04MTg0LWZhOGY1MjgxM2QxOA==<\/span><span>&quot;,\n<\/span><span>                    &quot;<\/span><span style=\"color:#a3be8c;\">hasNextPage<\/span><span>&quot;: <\/span><span style=\"color:#d08770;\">true\n<\/span><span>                }\n<\/span><span>            }\n<\/span><span>        }\n<\/span><span>    }\n<\/span><span>}\n<\/span><\/code><\/pre>\n<p>There's one detail you might've missed in the code for the <code>FriendList<\/code> component's fragment definition. I've included the <code>__id<\/code> field to help us uncover how the connection is identified via its Data ID. The value of this ID is:<\/p>\n<p><code>client:MTpVc2VyOjEyMw==:__user_friends_connection(search:{\"name\":\"\"})<\/code><\/p>\n<p>Let's break down how this Data ID is created with the following diagram:<\/p>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;understanding_relay_data_ids-05.png\"\n        loading=\"lazy\"\n        alt=\"Diagram of explanation of Relay&#x27;s connection Data IDConnection Data ID\"\n        width=\"600px\"\n        height=\"auto;\"\n      \/>\n      \n      \n        <figcaption>\n          Connection Data ID\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n<p>Why and how is this Data ID created? It appears similar to what we've previously observed with the <code>version<\/code>, <code>getUser<\/code> and <code>node<\/code> fields. In our schema, the <code>UserConnection<\/code> does not contain an <code>id<\/code> field. This means that Relay needs to generate the ID for us, which it accomplishes by concatenating several strings. The double colon <code>:<\/code> is used as a separator between these strings.<\/p>\n<p>We've touched on the <code>client<\/code> string before; it's a pre-determined value from the Relay library used as the <em>root<\/em> prefix. Up next, the Data ID of its parent type is utilized - in our case, this would be equivalent to the <code>id<\/code> field of the <code>User<\/code> that we've queried with <code>node<\/code> query.\nFinally, it uses the value of the <code>key<\/code> argument supplied to the <code>@connection<\/code> directive. We set it as <code>user_friends<\/code> in the <code>friendList_User<\/code> fragment. Interestingly, this key is prefixed with <code>__<\/code>. This final section of the Data ID also encompasses serialized arguments given to the connection, in this particular scenario <code>search<\/code>, which is an empty string right now. I'll delve into these elements shortly.<\/p>\n<p>Let's look at Relay dev tools to understand how it represents this connection with only first page loaded:<\/p>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;understanding_relay_data_ids-06.png\"\n        loading=\"lazy\"\n        alt=\"Relay dev tools with UserConnectionWait, what?\"\n        width=\"400px\"\n        height=\"auto;\"\n      \/>\n      \n      \n        <figcaption>\n          Wait, what?\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n<p>That's strange, it looks like there are two fields associated with our <code>User<\/code> record:<\/p>\n<ol>\n<li><code>__user_friends_connection(search:{\"name\":\"\"})<\/code><\/li>\n<li><code>friends(first: 10,search:{\"name\":\"\"})<\/code><\/li>\n<\/ol>\n<p>What\u2019s going on? Here's how I see it: any field that begins with an underscore(s), such as the first one, is an internal field that is somehow <em>special<\/em>. The field <code>__user_friends_connection(search:{\"name\":\"\"})<\/code> matches the suffix of the connection's Data ID. Let's fetch another page using <code>loadNext<\/code> and observe what happens.<\/p>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;understanding_relay_data_ids-07.png\"\n        loading=\"lazy\"\n        alt=\"Relay dev tools with UserConnection on second pageNext page fetched, more fields!\"\n        width=\"400px\"\n        height=\"auto;\"\n      \/>\n      \n      \n        <figcaption>\n          Next page fetched, more fields!\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n<p>Now we have additional <code>friends<\/code> field in our <code>User<\/code> record:<\/p>\n<ol>\n<li><code>__user_friends_connection(search:{\"name\":\"\"})<\/code><\/li>\n<li><code>friends(first: 10,search:{\"name\":\"\"})<\/code><\/li>\n<li><code>friends(after:\"MTpVc2VyOmMwYmIzYjY0LTJlZDgtNGRjNS04MTg0LWZhOGY1MjgxM2QxOA==\",first:10,search:{\"name\":\"\"})<\/code><\/li>\n<\/ol>\n<p>The third field signifies another page, since <code>after<\/code> is a cursor to get the next page and it's one of the connection arguments, it gets serialized as well.<\/p>\n<p>The <code>__id<\/code> field of the connection itself that we're rendering in <code>FriendList<\/code> component like so:<\/p>\n<pre data-lang=\"tsx\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-tsx \"><code class=\"language-tsx\" data-lang=\"tsx\"><span>&lt;<\/span><span style=\"color:#bf616a;\">pre<\/span><span>&gt;<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">data<\/span><span>.<\/span><span style=\"color:#bf616a;\">friends<\/span><span>.<\/span><span style=\"color:#bf616a;\">__id<\/span><span style=\"color:#ab7967;\">}<\/span><span>&lt;\/<\/span><span style=\"color:#bf616a;\">pre<\/span><span>&gt;\n<\/span><\/code><\/pre>\n<p>Is still equal to previous value and has same Data ID: <code>client:MTpVc2VyOjEyMw==:__user_friends_connection(search:{\"name\":\"\"})<\/code><\/p>\n<p>The way I interpret this is that the internal field <code>__user_friends_connection(search:{\"name\":\"\"})<\/code> is essentially read by the <code>usePaginationFragment<\/code> hook. This field amalgamates all the other pages (of the <code>UserConnection<\/code> types) so they can be shown as a single list in our user interface. It is capable of resolving those two fields (see #2 and #3 in the list above) on our <code>User<\/code> record and then treating them as one collective list. It uses the <code>after<\/code> argument to know that it needs to append it to the existing list. This process can be visualized by expanding the <code>__user_friends_connection(search:{\"name\":\"\"})<\/code> field in the development tools.<\/p>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;understanding_relay_data_ids-08.png\"\n        loading=\"lazy\"\n        alt=\"Expanded internal field for connectionIt&#x27;s all there\"\n        width=\"400px\"\n        height=\"auto;\"\n      \/>\n      \n      \n        <figcaption>\n          It&#x27;s all there\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n<p>Upon inspecting this field, you'll notice that it contains all 20 edges. This means two pages, as each page consists of 10 edges. If you were to use <code>loadNext(5)<\/code>, it would only append 5 records to this list. Much like the <code>after<\/code> argument, Relay is clever enough to understand that we still want to present all of this data as a single list in our UI.<\/p>\n<p>Imagine our UI has input to search through the friend list. We've already fetched two pages without any <code>search<\/code> argument. Now we pass value <code>\"Tim\"<\/code> as a search term.<\/p>\n<p>As a user you will expect that UI will clear our existing list and replace it with new one, containing only the filtered values. This time our Data ID rendered inside React as <code>data.friend.__id<\/code> has different value:<\/p>\n<p><code>client:MTpVc2VyOjEyMw==:__user_friends_connection(search:{\"name\":\"Tim\"})<\/code><\/p>\n<p>Our <code>search<\/code> argument to the connection gets serialized into its Data ID. The hook is now reading from different Data ID with its own internal field to which subsequent pages will be appended. In our case we only get 2 edges though:<\/p>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;understanding_relay_data_ids-09.png\"\n        loading=\"lazy\"\n        alt=\"New Data ID for connection with search argumentNew `friends` fields with search argument\"\n        width=\"400px\"\n        height=\"auto;\"\n      \/>\n      \n      \n        <figcaption>\n          New `friends` fields with search argument\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n<p>If our search results had more than 10 edges and we'd fetch another page, new field <code>friends(first:10,search:{\"name\":\"Tim\"},after:\"cursor\")<\/code> would appear in the store, its internal field <code>__user_friends_connection(search:{\"name\":\"Tim\"})<\/code> would be updated with these new edges.<\/p>\n<p>This mechanism provides very quick user feedback. In case the user clears the input field for search term, our previously fetched pages will appear instantly as they're read from the cache - very smart and efficient!<\/p>\n<p><strong>The difference between <code>__id<\/code> field queried via fragment versus how Data IDs are represented in the dev tools were the main source of confusion for me.<\/strong> You can see that Relay internally serializes all connection arguments such as <code>after<\/code>, <code>first<\/code> but they are missing from <code>__id<\/code> field. This is explained in the documentation<sup class=\"footnote-reference\"><a href=\"#4\">4<\/a><\/sup>:<\/p>\n<blockquote>\n<p>[...] each of the values passed in as connection filters will be used to identify the connection in the Relay store.\nNote that this excludes pagination arguments, i.e. it excludes first, last, before, and after.<\/p>\n<\/blockquote>\n<p>Our <code>search<\/code> field is part of <code>__id<\/code> field because we explicitly added it as <code>filters<\/code> argument to <code>@connection<\/code> directive in the <code>FriendList<\/code> component:<\/p>\n<pre data-lang=\"graphql\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-graphql \"><code class=\"language-graphql\" data-lang=\"graphql\"><span>@connection(key: <\/span><span style=\"color:#a3be8c;\">&quot;user_friends&quot;<\/span><span>, filters: [<\/span><span style=\"color:#a3be8c;\">&quot;search&quot;<\/span><span>])\n<\/span><\/code><\/pre>\n<p>This tells Relay to include it as part of the connection identity. For queries with connections that do not have any extra filtering, it is recommended to completely omit <code>filters<\/code> argument, the arguments such as <code>first<\/code>, <code>after<\/code>, <code>before<\/code> etc are implicit.<\/p>\n<p>For example if in our case we would <strong>not want<\/strong> to include <code>search<\/code> in Data ID, we can set it up like this:<\/p>\n<pre data-lang=\"graphql\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-graphql \"><code class=\"language-graphql\" data-lang=\"graphql\"><span>@connection(key: <\/span><span style=\"color:#a3be8c;\">&quot;user_friends&quot;<\/span><span>, filters: [])\n<\/span><\/code><\/pre>\n<p>The <code>__id<\/code> field will now have value <code>client:MTpVc2VyOjEyMw==:__user_friends_connection<\/code>.<\/p>\n<p>In some situations you might want to omit some argument to your connection field to keep the identity of the connection the same, regardless of the changed input. Relay will still create its internal fields like we've seen before but Relay hook will treat it as the same connection. It gets much more useful inside updater functions, where you can call <code>store.get(__id)<\/code> and be sure you'll get that specific connection.<\/p>\n<p>Lastly, I need to mention that Relay exposes set of utility function <code>ConnectionHandler.getConnection<\/code> that can generate Data ID as well. It is imperative way which requires you to pass record containing the connection, it's <code>key<\/code> argument and any other arguments so in practice, it means passing all the values around in order to access them. I find querying for <code>__id<\/code> to be more convenient and easy to use.<\/p>\n<hr \/>\n<div class=\"footnote-definition\" id=\"1\"><sup class=\"footnote-definition-label\">1<\/sup>\n<p>The store is a data structure that contains queried, cached or payload data related to GraphQL operations performed via Relay hooks. It offers many methods to read and write to the store. You can find more  in the <a href=\"https:\/\/relay.dev\/docs\/api-reference\/store\/\">official documentation<\/a>(<a href=\"https:\/\/web.archive.org\/web\/20240227201914\/https:\/\/relay.dev\/docs\/api-reference\/store\/\">archive<\/a>)<\/p>\n<\/div>\n<div class=\"footnote-definition\" id=\"2\"><sup class=\"footnote-definition-label\">2<\/sup>\n<p><a href=\"https:\/\/relay.dev\/graphql\/connections.htm\">https:\/\/relay.dev\/graphql\/connections.htm<\/a>(<a href=\"https:\/\/web.archive.org\/web\/20240407194949\/https:\/\/relay.dev\/graphql\/connections.htm\">archive<\/a>)<\/p>\n<\/div>\n<div class=\"footnote-definition\" id=\"3\"><sup class=\"footnote-definition-label\">3<\/sup>\n<p><a href=\"https:\/\/relay.dev\/docs\/next\/guided-tour\/updating-data\/imperatively-modifying-store-data-unsafe\/#the-various-types-of-updater-functions\">https:\/\/relay.dev\/docs\/next\/guided-tour\/updating-data\/imperatively-modifying-store-data-unsafe\/#the-various-types-of-updater-functions<\/a>(<a href=\"https:\/\/web.archive.org\/web\/20240407195335\/https:\/\/relay.dev\/docs\/next\/guided-tour\/updating-data\/imperatively-modifying-store-data-unsafe\/#the-various-types-of-updater-functions\">archive<\/a>)<\/p>\n<\/div>\n<div class=\"footnote-definition\" id=\"4\"><sup class=\"footnote-definition-label\">4<\/sup>\n<p><a href=\"https:\/\/relay.dev\/docs\/guided-tour\/list-data\/updating-connections\/#connection-identity-with-filters\">https:\/\/relay.dev\/docs\/guided-tour\/list-data\/updating-connections\/#connection-identity-with-filters<\/a>(<a href=\"https:\/\/web.archive.org\/web\/20240407204551\/https:\/\/relay.dev\/docs\/guided-tour\/list-data\/updating-connections\/#connection-identity-with-filters\">archive<\/a>)<\/p>\n<\/div>\n"},{"title":"Konrad Zuse","published":"2024-04-03T08:40:36+00:00","updated":"2024-04-03T08:40:36+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/konrad-zuse\/"}},"id":"https:\/\/synacek.org\/links\/konrad-zuse\/","content":{"@attributes":{"type":"html"}}},{"title":"Navigating the future of frontend","published":"2024-03-20T19:33:21+00:00","updated":"2024-03-20T19:33:21+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/navigating-the-future-of-frontend\/"}},"id":"https:\/\/synacek.org\/links\/navigating-the-future-of-frontend\/","content":{"@attributes":{"type":"html"}}},{"title":"Measuring Developer Productivity via Humans","published":"2024-03-15T08:07:56+00:00","updated":"2024-03-15T08:07:56+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/measuring-developer-productivity-via-humans\/"}},"id":"https:\/\/synacek.org\/links\/measuring-developer-productivity-via-humans\/","content":{"@attributes":{"type":"html"}}},{"title":"The await event horizon in JavaScript","published":"2024-01-12T07:56:57+00:00","updated":"2024-01-12T07:56:57+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/the-await-event-horizon-in-javascript\/"}},"id":"https:\/\/synacek.org\/links\/the-await-event-horizon-in-javascript\/","content":{"@attributes":{"type":"html"}}},{"title":"ooh.directory bot","published":"2023-12-31T00:00:00+00:00","updated":"2023-12-31T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/fun\/ooh-directory-bot\/"}},"id":"https:\/\/synacek.org\/code\/fun\/ooh-directory-bot\/","content":{"@attributes":{"type":"html"}}},{"title":"Is software getting worse? - Stack Overflow","published":"2023-12-27T11:56:28+00:00","updated":"2023-12-27T11:56:28+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/is-software-getting-worse-stack-overflow\/"}},"id":"https:\/\/synacek.org\/links\/is-software-getting-worse-stack-overflow\/","content":{"@attributes":{"type":"html"}}},{"title":"gh-co.nvim","published":"2023-11-25T00:00:00+00:00","updated":"2023-11-25T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/utilities\/github-codeowners-neovim-plugin\/"}},"id":"https:\/\/synacek.org\/code\/utilities\/github-codeowners-neovim-plugin\/","content":{"@attributes":{"type":"html"}}},{"title":"Code Colocation is King | Koen van Gilst","published":"2023-11-21T17:36:43+00:00","updated":"2023-11-21T17:36:43+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/code-colocation-is-king-koen-van-gilst\/"}},"id":"https:\/\/synacek.org\/links\/code-colocation-is-king-koen-van-gilst\/","content":{"@attributes":{"type":"html"}}},{"title":"How platform teams get stuff done","published":"2023-11-18T12:35:40+00:00","updated":"2023-11-18T12:35:40+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/how-platform-teams-get-stuff-done\/"}},"id":"https:\/\/synacek.org\/links\/how-platform-teams-get-stuff-done\/","content":{"@attributes":{"type":"html"}}},{"title":"No one actually wants simplicity - lukeplant.me.uk","published":"2023-08-23T14:49:45+00:00","updated":"2023-08-23T14:49:45+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/no-one-actually-wants-simplicity-lukeplant-me-uk\/"}},"id":"https:\/\/synacek.org\/links\/no-one-actually-wants-simplicity-lukeplant-me-uk\/","content":{"@attributes":{"type":"html"}}},{"title":"Things you forgot (or never knew) because of React","published":"2023-08-20T18:51:46+00:00","updated":"2023-08-20T18:51:46+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/things-you-forgot-or-never-knew-because-of-react\/"}},"id":"https:\/\/synacek.org\/links\/things-you-forgot-or-never-knew-because-of-react\/","content":{"@attributes":{"type":"html"}}},{"title":"The Terminal \u2022 furbo.org","published":"2023-08-12T10:59:28+00:00","updated":"2023-08-12T10:59:28+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/the-terminal-furbo-org\/"}},"id":"https:\/\/synacek.org\/links\/the-terminal-furbo-org\/","content":"<p>Older but still relevant collection of macOS specific terminal tips.<\/p>\n"},{"title":"AI and the automation of work \u2014 Benedict Evans","published":"2023-07-11T10:35:21+00:00","updated":"2023-07-11T10:35:21+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/ai-and-the-automation-of-work-benedict-evans\/"}},"id":"https:\/\/synacek.org\/links\/ai-and-the-automation-of-work-benedict-evans\/","content":{"@attributes":{"type":"html"}}},{"title":"czdomains","published":"2023-06-27T00:00:00+00:00","updated":"2023-06-27T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/projects\/czdomains\/"}},"id":"https:\/\/synacek.org\/code\/projects\/czdomains\/","content":"<p>Web application that allows you to browse or search through database of Czechia TLD (<code>.cz<\/code>) domains. The database contains bulk of historically registered TLDs since 1990s. The application can also request WHOIS data and Internet Archive's Wayback machine archival link.<\/p>\n<p>This project was created with main goal being <strong>simplicity<\/strong>. Even if it's written in Typescript, the front-end is old-fashioned HTML (nunjucks) templates. The database is just <code>sqlite<\/code> file that contains all the entries making the deployment really straightforward. The design is intentional and is a throwback to mid-1990s web, even the icons were sourced from CD-ROM containing clip-art graphics.<\/p>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;czdomains-01.png\"\n        loading=\"lazy\"\n        alt=\"Home page of czdomains web applicationHome page sporting early-web classical look\"\n        width=\"400px\"\n        height=\"auto;\"\n      \/>\n      \n      \n        <figcaption>\n          Home page sporting early-web classical look\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n<p>I wanted to have some kind of simple web application that I could experiment with, hosting it on own VPS and setting it all up. I was interested in trying out Typescript-based web framework with server-side rendered HTML, without using any client-side Javascript - the design and type of the application does not really need it.<\/p>\n<p>The project was originally written for Deno runtime using <a href=\"https:\/\/fresh.deno.dev\/\">Fresh web framework<\/a> (see <a href=\"https:\/\/github.com\/comatory\/czdomains\/pull\/1\">git history<\/a>) but I ultimately decided to switch to <a href=\"https:\/\/www.fastify.io\/\">fastify web framework<\/a>. The reasons at the time of writing the application (end of 2022 \/ early 2023) were:<\/p>\n<ul>\n<li>Fresh did not feel mature enough: inability to specify custom views for error pages<\/li>\n<li>Fresh felt like it didn't come with batteries included: like more advanced routing, support for cookies<\/li>\n<li>Fastify has way better documentation<\/li>\n<li>Fastify comes with JSON schema which eliminates lot of defensive code for HTTP endpoints that I'd otherwise have to write by hand when using Fresh<\/li>\n<\/ul>\n<p>Overall it was good experience rewriting the project and comparing the pros and cons. I think Fresh framework was otherwise very cool and I really appreciate its <em>islands<\/em> architecture (\"ship HTML first\"), I also prefer Deno's dependency management over centralized NPM registry.<\/p>\n<div class=\"row wrap centered\">\n  \n\n\n  \n\n\n<div class=\" image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;czdomains-02.png\"\n        loading=\"lazy\"\n        alt=\"Browsing the list of domainsPaginated results\"\n        width=\"160px\"\n        height=\"auto;\"\n      \/>\n      \n      \n        <figcaption>\n          Paginated results\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n\n  \n\n\n  \n\n\n<div class=\" image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;czdomains-03.png\"\n        loading=\"lazy\"\n        alt=\"Detail of a domainDomain detail\"\n        width=\"160px\"\n        height=\"auto;\"\n      \/>\n      \n      \n        <figcaption>\n          Domain detail\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n\n<\/div>\n"},{"title":"Memory Allocation","published":"2023-05-25T07:28:02+00:00","updated":"2023-05-25T07:28:02+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/memory-allocation\/"}},"id":"https:\/\/synacek.org\/links\/memory-allocation\/","content":{"@attributes":{"type":"html"}}},{"title":"Exhaustive switch statements in Typescript","published":"2023-05-11T21:00:00+00:00","updated":"2023-05-11T21:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/log\/exhaustive-switch-statements-in-typescript\/"}},"id":"https:\/\/synacek.org\/log\/exhaustive-switch-statements-in-typescript\/","content":"<p>I was looking for a way to exhaustively type-check <code>switch<\/code> statements when writing Typescript. I went through few iterations with a colleague at work before we arrived at something that worked.<\/p>\n<p>The goal is to always cover all possible cases using <code>case<\/code>, similarly how language such as Rust forces you to return value as <code>Option&lt;...&gt;<\/code>.<\/p>\n<p><em>Note: All code examples were tested on Typescript v5.1.6<\/em><\/p>\n<p>Imagine the following example code:<\/p>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span>    <\/span><span style=\"color:#b48ead;\">type <\/span><span>Id = &quot;<\/span><span style=\"color:#a3be8c;\">A<\/span><span>&quot; | &quot;<\/span><span style=\"color:#a3be8c;\">B<\/span><span>&quot; | &quot;<\/span><span style=\"color:#a3be8c;\">C<\/span><span>&quot;;\n<\/span><span>\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">function <\/span><span style=\"color:#8fa1b3;\">getId<\/span><span>(<\/span><span style=\"color:#bf616a;\">id<\/span><span>: Id) {\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">switch <\/span><span>(<\/span><span style=\"color:#bf616a;\">id<\/span><span>) {\n<\/span><span>        <\/span><span style=\"color:#b48ead;\">case <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">A<\/span><span>&quot;:\n<\/span><span>          <\/span><span style=\"color:#b48ead;\">return <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">first<\/span><span>&quot;;\n<\/span><span>        <\/span><span style=\"color:#b48ead;\">case <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">B<\/span><span>&quot;:\n<\/span><span>          <\/span><span style=\"color:#b48ead;\">return <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">second<\/span><span>&quot;;\n<\/span><span>        <\/span><span style=\"color:#b48ead;\">default<\/span><span>:\n<\/span><span>          <\/span><span style=\"color:#b48ead;\">return <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">third<\/span><span>&quot;;\n<\/span><span>      }\n<\/span><span>    }\n<\/span><\/code><\/pre>\n<p>Now we want to make sure that Typescript compiler throws an error because case for value \"C\" is not covered.<\/p>\n<p>Simple solution would be to omit <code>default<\/code> value like this:<\/p>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span>    <\/span><span style=\"color:#b48ead;\">function <\/span><span style=\"color:#8fa1b3;\">getId<\/span><span>(<\/span><span style=\"color:#bf616a;\">id<\/span><span>: Id): &#39;<\/span><span style=\"color:#a3be8c;\">first<\/span><span>&#39; | &#39;<\/span><span style=\"color:#a3be8c;\">second<\/span><span>&#39; {\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">switch <\/span><span>(<\/span><span style=\"color:#bf616a;\">id<\/span><span>) {\n<\/span><span>        <\/span><span style=\"color:#b48ead;\">case <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">A<\/span><span>&quot;:\n<\/span><span>          <\/span><span style=\"color:#b48ead;\">return <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">first<\/span><span>&quot;;\n<\/span><span>        <\/span><span style=\"color:#b48ead;\">case <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">B<\/span><span>&quot;:\n<\/span><span>          <\/span><span style=\"color:#b48ead;\">return <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">second<\/span><span>&quot;;\n<\/span><span>      }\n<\/span><span>    }\n<\/span><\/code><\/pre>\n<p>Sure it does work, but this solution assumes that:<\/p>\n<ul>\n<li>The <code>switch<\/code> statement is always inside a function that returns primitive types - so you also need to make sure that you type the return values explicitly.<\/li>\n<li>You must explicitly omit <code>default<\/code> case for this to work. More often than not, the codebase you're working on might have lint rule such as <code>default-case<\/code> enabled and will force you to add the default case - which is good! But then you're unable to use this pattern.<\/li>\n<\/ul>\n<p>Maybe you might have seen built-in type <code>never<\/code> and you wondered what it's good for? Well, it can be useful for making sure you can fail something, we can leverage this type to make sure default case never happens.<\/p>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#b48ead;\">type <\/span><span>Id = &quot;<\/span><span style=\"color:#a3be8c;\">A<\/span><span>&quot; | &quot;<\/span><span style=\"color:#a3be8c;\">B<\/span><span>&quot; | &quot;<\/span><span style=\"color:#a3be8c;\">C<\/span><span>&quot;;\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">function <\/span><span style=\"color:#8fa1b3;\">noop<\/span><span>(<\/span><span style=\"color:#bf616a;\">_<\/span><span>: never) {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">return<\/span><span>;\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">function <\/span><span style=\"color:#8fa1b3;\">getId<\/span><span>(<\/span><span style=\"color:#bf616a;\">id<\/span><span>: Id) {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">switch <\/span><span>(<\/span><span style=\"color:#bf616a;\">id<\/span><span>) {\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">case <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">A<\/span><span>&quot;:\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">return <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">first<\/span><span>&quot;;\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">case <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">B<\/span><span>&quot;:\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">return <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">second<\/span><span>&quot;;\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">default<\/span><span>:\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">return <\/span><span style=\"color:#8fa1b3;\">noop<\/span><span>(<\/span><span style=\"color:#bf616a;\">id<\/span><span>);\n<\/span><span>  }\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#8fa1b3;\">getId<\/span><span>(&quot;<\/span><span style=\"color:#a3be8c;\">B<\/span><span>&quot;);\n<\/span><\/code><\/pre>\n<p>Now you're getting this type-checking error:<\/p>\n<pre data-lang=\"shell\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-shell \"><code class=\"language-shell\" data-lang=\"shell\"><span>Argument of type &#39;string&#39; is not assignable to parameter of type &#39;never&#39;\n<\/span><\/code><\/pre>\n<p>The \"trick\" here is simple: function <code>noop<\/code> cannot ever receive any argument. And since there is missing case for \"C\", Typescript compiler correctly assumes that there's a real possibility of an argument being passed to the function.<\/p>\n<p>\u2705 When you correct this mistake, the error goes away:<\/p>\n<pre data-lang=\"typescript\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-typescript \"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#b48ead;\">type <\/span><span>Id = &quot;<\/span><span style=\"color:#a3be8c;\">A<\/span><span>&quot; | &quot;<\/span><span style=\"color:#a3be8c;\">B<\/span><span>&quot; | &quot;<\/span><span style=\"color:#a3be8c;\">C<\/span><span>&quot;;\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">function <\/span><span style=\"color:#8fa1b3;\">noop<\/span><span>(<\/span><span style=\"color:#bf616a;\">_<\/span><span>: never) {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">return<\/span><span>;\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">function <\/span><span style=\"color:#8fa1b3;\">getId<\/span><span>(<\/span><span style=\"color:#bf616a;\">id<\/span><span>: Id) {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">switch <\/span><span>(<\/span><span style=\"color:#bf616a;\">id<\/span><span>) {\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">case <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">A<\/span><span>&quot;:\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">return <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">first<\/span><span>&quot;;\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">case <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">B<\/span><span>&quot;:\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">return <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">second<\/span><span>&quot;;\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">case <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">C<\/span><span>&quot;:\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">return <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">third<\/span><span>&quot;;\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">default<\/span><span>:\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">return <\/span><span style=\"color:#8fa1b3;\">noop<\/span><span>(<\/span><span style=\"color:#bf616a;\">id<\/span><span>);\n<\/span><span>  }\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#8fa1b3;\">getId<\/span><span>(&quot;<\/span><span style=\"color:#a3be8c;\">B<\/span><span>&quot;);\n<\/span><\/code><\/pre>\n<p>The secret sauce is defining <code>noop<\/code>, I've also seen this function being called <code>absurd<\/code>.<\/p>\n<p>I already mentioned React and how this can be leveraged when returning <code>JSX<\/code> from the component. I give you the following example in which I have <code>OkComponent<\/code> and <code>ErrorComponent<\/code> which render child components depending on their <code>type<\/code> prop:<\/p>\n<pre data-lang=\"tsx\" style=\"background-color:#2b303b;color:#c0c5ce;\" class=\"language-tsx \"><code class=\"language-tsx\" data-lang=\"tsx\"><span style=\"color:#b48ead;\">import <\/span><span style=\"color:#bf616a;\">React <\/span><span style=\"color:#b48ead;\">from <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">react<\/span><span>&quot;;\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">type <\/span><span>Data =\n<\/span><span>  | { <\/span><span style=\"color:#bf616a;\">type<\/span><span>: &quot;<\/span><span style=\"color:#a3be8c;\">LOG<\/span><span>&quot;; <\/span><span style=\"color:#bf616a;\">name<\/span><span>: string }\n<\/span><span>  | { <\/span><span style=\"color:#bf616a;\">type<\/span><span>: &quot;<\/span><span style=\"color:#a3be8c;\">WARN<\/span><span>&quot;; <\/span><span style=\"color:#bf616a;\">name<\/span><span>: string }\n<\/span><span>  | { <\/span><span style=\"color:#bf616a;\">type<\/span><span>: &quot;<\/span><span style=\"color:#a3be8c;\">ERROR<\/span><span>&quot;; <\/span><span style=\"color:#bf616a;\">name<\/span><span>: string };\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">function <\/span><span style=\"color:#8fa1b3;\">absurd<\/span><span>&lt;A&gt;(<\/span><span style=\"color:#bf616a;\">_<\/span><span>: never): A {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">throw <\/span><span>new Error(&quot;<\/span><span style=\"color:#a3be8c;\">Called `absurd` function which should be uncallable<\/span><span>&quot;);\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">function <\/span><span style=\"color:#8fa1b3;\">Log<\/span><span>({ <\/span><span style=\"color:#bf616a;\">name <\/span><span>}: { <\/span><span style=\"color:#bf616a;\">name<\/span><span>: string }) {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">return <\/span><span>&lt;<\/span><span style=\"color:#bf616a;\">span<\/span><span>&gt;Logged <\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">name<\/span><span style=\"color:#ab7967;\">}<\/span><span>&lt;\/<\/span><span style=\"color:#bf616a;\">span<\/span><span>&gt;;\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">function <\/span><span style=\"color:#8fa1b3;\">Warn<\/span><span>({ <\/span><span style=\"color:#bf616a;\">name <\/span><span>}: { <\/span><span style=\"color:#bf616a;\">name<\/span><span>: string }) {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">return <\/span><span>&lt;<\/span><span style=\"color:#bf616a;\">span <\/span><span style=\"color:#d08770;\">style<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span>{ color: &quot;<\/span><span style=\"color:#a3be8c;\">yellow<\/span><span>&quot; }<\/span><span style=\"color:#ab7967;\">}<\/span><span>&gt;Warning <\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">name<\/span><span style=\"color:#ab7967;\">}<\/span><span>&lt;\/<\/span><span style=\"color:#bf616a;\">span<\/span><span>&gt;;\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#b48ead;\">function <\/span><span style=\"color:#8fa1b3;\">Err<\/span><span>({ <\/span><span style=\"color:#bf616a;\">name <\/span><span>}: { <\/span><span style=\"color:#bf616a;\">name<\/span><span>: string }) {\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">return <\/span><span>&lt;<\/span><span style=\"color:#bf616a;\">span <\/span><span style=\"color:#d08770;\">style<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span>{ color: &quot;<\/span><span style=\"color:#a3be8c;\">red<\/span><span>&quot; }<\/span><span style=\"color:#ab7967;\">}<\/span><span>&gt;Error <\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">name<\/span><span style=\"color:#ab7967;\">}<\/span><span>&lt;\/<\/span><span style=\"color:#bf616a;\">span<\/span><span>&gt;;\n<\/span><span>}\n<\/span><span>\n<\/span><span style=\"color:#65737e;\">\/\/ \u2705 This component will run OK and compiles without any error\n<\/span><span style=\"color:#b48ead;\">const <\/span><span style=\"color:#8fa1b3;\">OkComponent <\/span><span>= (<\/span><span style=\"color:#bf616a;\">props<\/span><span>: Data) <\/span><span style=\"color:#b48ead;\">=&gt; <\/span><span>{\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">switch <\/span><span>(<\/span><span style=\"color:#bf616a;\">props<\/span><span>.type) {\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">case <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">LOG<\/span><span>&quot;:\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">return <\/span><span>&lt;<\/span><span style=\"color:#ebcb8b;\">Log <\/span><span style=\"color:#d08770;\">name<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">props<\/span><span>.name<\/span><span style=\"color:#ab7967;\">} <\/span><span>\/&gt;;\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">case <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">WARN<\/span><span>&quot;:\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">return <\/span><span>&lt;<\/span><span style=\"color:#ebcb8b;\">Warn <\/span><span style=\"color:#d08770;\">name<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">props<\/span><span>.name<\/span><span style=\"color:#ab7967;\">} <\/span><span>\/&gt;;\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">case <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">ERROR<\/span><span>&quot;:\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">return <\/span><span>&lt;<\/span><span style=\"color:#ebcb8b;\">Err <\/span><span style=\"color:#d08770;\">name<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">props<\/span><span>.name<\/span><span style=\"color:#ab7967;\">} <\/span><span>\/&gt;;\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">default<\/span><span>:\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">return <\/span><span style=\"color:#8fa1b3;\">absurd<\/span><span>(<\/span><span style=\"color:#bf616a;\">props<\/span><span>);\n<\/span><span>  }\n<\/span><span>};\n<\/span><span>\n<\/span><span style=\"color:#65737e;\">\/\/ \u274c This component will fail and compile with an error\n<\/span><span style=\"color:#b48ead;\">const <\/span><span style=\"color:#8fa1b3;\">ErrorComponent <\/span><span>= (<\/span><span style=\"color:#bf616a;\">props<\/span><span>: Data) <\/span><span style=\"color:#b48ead;\">=&gt; <\/span><span>{\n<\/span><span>  <\/span><span style=\"color:#b48ead;\">switch <\/span><span>(<\/span><span style=\"color:#bf616a;\">props<\/span><span>.type) {\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">case <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">LOG<\/span><span>&quot;:\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">return <\/span><span>&lt;<\/span><span style=\"color:#ebcb8b;\">Log <\/span><span style=\"color:#d08770;\">name<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">props<\/span><span>.name<\/span><span style=\"color:#ab7967;\">} <\/span><span>\/&gt;;\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">case <\/span><span>&quot;<\/span><span style=\"color:#a3be8c;\">WARN<\/span><span>&quot;:\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">return <\/span><span>&lt;<\/span><span style=\"color:#ebcb8b;\">Warn <\/span><span style=\"color:#d08770;\">name<\/span><span>=<\/span><span style=\"color:#ab7967;\">{<\/span><span style=\"color:#bf616a;\">props<\/span><span>.name<\/span><span style=\"color:#ab7967;\">} <\/span><span>\/&gt;;\n<\/span><span>    <\/span><span style=\"color:#b48ead;\">default<\/span><span>:\n<\/span><span>      <\/span><span style=\"color:#b48ead;\">return <\/span><span style=\"color:#8fa1b3;\">absurd<\/span><span>(<\/span><span style=\"color:#bf616a;\">props<\/span><span>);\n<\/span><span>  }\n<\/span><span>};\n<\/span><\/code><\/pre>\n<p>This example shows you how the exhaustive <code>switch<\/code> statement can be made to work with non-primitive types such as JavaScript object.<\/p>\n<p>On top of this, the <code>absurd<\/code> function also throws. This means not only you get error from Typescript compiler, but you also get one from the JavaScript run-time, however this might be something you might <em>not<\/em> want to do. It depends on your use case, perhaps you'd like to log the error to your logging service instead.<\/p>\n<p><em>Tip: when using <code>absurd<\/code> with <code>throw<\/code> in React, it will trigger error boundary.<\/em><\/p>\n<p>This pattern might not fit everyone. It requires developers to always add <code>absurd<\/code> function to their default case. Abstracting it away is possible, but I think it makes the code more opaque. It might look out of place in some code bases, the concept feels more aligned with functional style of programming.<\/p>\n<p>But for those who like to have their execution paths well covered I would say it's a good approach. This pattern could be supported by linter where each <code>switch<\/code> would require calling <code>absurd<\/code> in the default case.<\/p>\n"},{"title":"principles - Nabeel Qureshi","published":"2023-04-29T08:39:42+00:00","updated":"2023-04-29T08:39:42+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/principles-nabeel-qureshi\/"}},"id":"https:\/\/synacek.org\/links\/principles-nabeel-qureshi\/","content":{"@attributes":{"type":"html"}}},{"title":"AIs can write for us but will we actually want them to? | Bryan Braun - Frontend Developer","published":"2023-04-17T10:51:03+00:00","updated":"2023-04-17T10:51:03+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/ais-can-write-for-us-but-will-we-actually-want-the\/"}},"id":"https:\/\/synacek.org\/links\/ais-can-write-for-us-but-will-we-actually-want-the\/","content":"<p>\u201cWriting is a moment for self-reflection, for providing the space and time necessary for the conception of thoughts or feelings that can change your heart or mind. Offloading that task to AI is not necessarily a net-gain, it is a trade-off. One to make consciously.\u201d<\/p>\n"},{"title":"Prompt injection: what\u2019s the worst that can happen?","published":"2023-04-14T18:20:22+00:00","updated":"2023-04-14T18:20:22+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/prompt-injection-what-s-the-worst-that-can-happen\/"}},"id":"https:\/\/synacek.org\/links\/prompt-injection-what-s-the-worst-that-can-happen\/","content":{"@attributes":{"type":"html"}}},{"title":"Switching to C Over \u2018Modern\u2019 Programming Languages","published":"2023-04-13T06:42:56+00:00","updated":"2023-04-13T06:42:56+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/switching-to-c-over-modern-programming-languages\/"}},"id":"https:\/\/synacek.org\/links\/switching-to-c-over-modern-programming-languages\/","content":"<p>I'm kind of on the opposite end as the author is, my first language(s) were scripting\/dynamic languages so those click for me right away.<\/p>\n"},{"title":"Database \u201csharding\u201d came from UO? \u2013 Raph's Website","published":"2023-04-10T16:55:38+00:00","updated":"2023-04-10T16:55:38+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/database-sharding-came-from-uo-raph-s-website\/"}},"id":"https:\/\/synacek.org\/links\/database-sharding-came-from-uo-raph-s-website\/","content":{"@attributes":{"type":"html"}}},{"title":"Merging with diff3 \u2013 The If Works","published":"2023-04-09T19:19:11+00:00","updated":"2023-04-09T19:19:11+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/merging-with-diff3-the-if-works\/"}},"id":"https:\/\/synacek.org\/links\/merging-with-diff3-the-if-works\/","content":{"@attributes":{"type":"html"}}},{"title":"Simply explained: how does GPT work? | Confused bit","published":"2023-04-09T00:29:14+00:00","updated":"2023-04-09T00:29:14+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/simply-explained-how-does-gpt-work-confused-bi\/"}},"id":"https:\/\/synacek.org\/links\/simply-explained-how-does-gpt-work-confused-bi\/","content":"<p>Nice summary of LLMs for common people.<\/p>\n"},{"title":"The Day Windows Died","published":"2023-04-09T00:29:14+00:00","updated":"2023-04-09T00:29:14+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/the-day-windows-died\/"}},"id":"https:\/\/synacek.org\/links\/the-day-windows-died\/","content":{"@attributes":{"type":"html"}}},{"title":"Fugitive.vim - browsing the git object database","published":"2023-04-06T06:52:12+00:00","updated":"2023-04-06T06:52:12+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/fugitive-vim-browsing-the-git-object-database\/"}},"id":"https:\/\/synacek.org\/links\/fugitive-vim-browsing-the-git-object-database\/","content":{"@attributes":{"type":"html"}}},{"title":"Types without TypeScript","published":"2023-04-04T19:26:03+00:00","updated":"2023-04-04T19:26:03+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/types-without-typescript\/"}},"id":"https:\/\/synacek.org\/links\/types-without-typescript\/","content":{"@attributes":{"type":"html"}}},{"title":"What Happened to the API Economy?. \u2022 Kostas Heaven on Net","published":"2023-04-04T14:33:49+00:00","updated":"2023-04-04T14:33:49+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/what-happened-to-the-api-economy-kostas-heaven\/"}},"id":"https:\/\/synacek.org\/links\/what-happened-to-the-api-economy-kostas-heaven\/","content":{"@attributes":{"type":"html"}}},{"title":"Use Maps More and Objects Less","published":"2023-03-15T16:45:08+00:00","updated":"2023-03-15T16:45:08+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/use-maps-more-and-objects-less\/"}},"id":"https:\/\/synacek.org\/links\/use-maps-more-and-objects-less\/","content":{"@attributes":{"type":"html"}}},{"title":"The History of the URL","published":"2023-03-09T10:51:22+00:00","updated":"2023-03-09T10:51:22+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/the-history-of-the-url\/"}},"id":"https:\/\/synacek.org\/links\/the-history-of-the-url\/","content":{"@attributes":{"type":"html"}}},{"title":"GOGathon: Broken Swords From a Faulty Forge","published":"2023-03-09T09:05:41+00:00","updated":"2023-03-09T09:05:41+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/gogathon\/"}},"id":"https:\/\/synacek.org\/links\/gogathon\/","content":"<p>Good take on one of my gavorite gaming series Broken Sword. It's a rundown of all the available sequels (as of 2023): 1 - 5.<\/p>\n"},{"title":"Well-known Symbols","published":"2023-03-08T22:07:05+00:00","updated":"2023-03-08T22:07:05+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/well-known-symbols\/"}},"id":"https:\/\/synacek.org\/links\/well-known-symbols\/","content":{"@attributes":{"type":"html"}}},{"title":"Welcome to my GUI Gallery","published":"2023-03-05T22:01:36+00:00","updated":"2023-03-05T22:01:36+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/welcome-to-my-gui-gallery\/"}},"id":"https:\/\/synacek.org\/links\/welcome-to-my-gui-gallery\/","content":"<p>Nice deep-dive on different UIs on various desktop operating systems.<\/p>\n"},{"title":"Vim Fugitive in action","published":"2023-03-05T21:46:45+00:00","updated":"2023-03-05T21:46:45+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/vim-fugitive-in-action\/"}},"id":"https:\/\/synacek.org\/links\/vim-fugitive-in-action\/","content":{"@attributes":{"type":"html"}}},{"title":"You Don't Need Rails to Start Using Hotwire","published":"2023-03-02T19:56:00+00:00","updated":"2023-03-02T19:56:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/links\/you-dont-need-rails-to-start-using-hotwire\/"}},"id":"https:\/\/synacek.org\/links\/you-dont-need-rails-to-start-using-hotwire\/","content":"<p>I haven't looked at Turbo-related tech in a while and never thought of using it outside of Rails. Seems like a nice light-weight way of creating somewhat dynamic sites without Javascript overhead.<\/p>\n"},{"title":"HTML Tetris","published":"2022-11-20T00:00:00+00:00","updated":"2022-11-20T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/fun\/html-tetris\/"}},"id":"https:\/\/synacek.org\/code\/fun\/html-tetris\/","content":"<p>Motivation for re-implementing the game was to:<\/p>\n<ul>\n<li>understand the mechanics of a tetris game<\/li>\n<li>try it game development with something simple-ish<\/li>\n<\/ul>\n<p>I believe I achieved those goals and it was quite interesting to learn about various styles of tetris games. One example was reading up on different types of rotation systems, meaning how pieces are rotated and how they behave when collision occurs<sup class=\"footnote-reference\"><a href=\"#1\">1<\/a><\/sup>.<\/p>\n<p>I didn't use <code>Canvas<\/code> API for browsers but instead opted for rendering the game using HTML document itself. The play area is constructed with CSS grid. This decision meant not using \"sprites\". The block that makes up a tetromino shape isn't image either, it's pure CSS - I published it as <a href=\"https:\/\/github.com\/comatory\/css-tetrominoes\">separate source<\/a>.<\/p>\n<p>See for yourself:<\/p>\n\n\n\n  \n\n\n<div class=\"centered video\">\n  \n  <figure>\n  \n    <video\n     controls=\"\"\n     muted=\"\"\n     title=\"Tetris game\"\n     src=\"&#x2F;video&#x2F;html-tetris.mov\"\n     class=\"responsive-media\"\n     width=\"600px\"\n     height=\"auto\"\n    >\n    <\/video>\n    \n      <figcaption>\n        Gameplay footage of Tetris game\n      <\/figcaption>\n    \n  \n  <\/figure>\n  \n<\/div>\n<p>Another goal of my implementation was to approach it with simplicity in mind. I'm using plain Javascript and regular DOM APIs. I didn't want any build steps <sup class=\"footnote-reference\"><a href=\"#2\">2<\/a><\/sup> but also wanted to have the code modularized. I went with native <a href=\"https:\/\/developer.mozilla.org\/en-US\/\">ES modules<\/a>.<\/p>\n<p>Having static types is nice too but that would mean introducing Typescript (and a build step). Instead I chose <a href=\"https:\/\/jsdoc.app\/\">JSDoc<\/a> annotations which works quite well for the project of this size.<\/p>\n<p>I went for pixel-art look, more specifically I wanted the game to be similar to <a href=\"https:\/\/en.wikipedia.org\/wiki\/Tetris_(Game_Boy_video_game)\">1989 Gameboy release<\/a>. The game is meant to be played on desktop computers with keyboard but the game can accomodate mobile devices as well, touch controls will show up on smaller screens or they can be toggled via an option control.<\/p>\n\n\n\n  \n\n\n<div class=\"centered responsive-media image\">\n  <wc-lightbox>\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;html-tetris.png\"\n        loading=\"lazy\"\n        alt=\"Image of HTML Tetris game\"\n        width=\"400px\"\n        height=\"auto;\"\n      \/>\n      \n      \n    \n  <\/wc-lightbox>\n<\/div>\n<p>Game is installable as a PWA so it can run offline. Generally this was interesting challenge. I found out that getting some things right is quite tricky - correct timing and animations. My implementation is not perfect on that front but it is playable. It definitely took more time than I expected it to.<\/p>\n<hr \/>\n<div class=\"footnote-definition\" id=\"1\"><sup class=\"footnote-definition-label\">1<\/sup>\n<p>My game uses NES-style rotation system. If you try to rotate a tetromino in a way where it would bump into a wall or other fallen tetrominoes, it will not succeed. It's pretty old school approach, today there are way more advanced systems such as <a href=\"https:\/\/tetris.wiki\/Super_Rotation_System\"><em>super rotation system<\/em><\/a> which are more user-friendly.<\/p>\n<\/div>\n<div class=\"footnote-definition\" id=\"2\"><sup class=\"footnote-definition-label\">2<\/sup>\n<p>There are few exceptions but they do not involve the runtime. I used some developer tooling for formatting, testing and linting. I also wrote little script that generates list of files to be cached by a service worker.<\/p>\n<\/div>\n"},{"title":"CSS Tetrominoes","published":"2022-11-19T00:00:00+00:00","updated":"2022-11-19T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/fun\/css-tetrominoes\/"}},"id":"https:\/\/synacek.org\/code\/fun\/css-tetrominoes\/","content":{"@attributes":{"type":"html"}}},{"title":"wdid","published":"2022-07-22T00:00:00+00:00","updated":"2022-07-22T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/projects\/wdid\/"}},"id":"https:\/\/synacek.org\/code\/projects\/wdid\/","content":{"@attributes":{"type":"html"}}},{"title":"Mileus mobile apps","published":"2022-02-01T00:00:00+00:00","updated":"2022-02-01T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/projects\/mileus-mobile\/"}},"id":"https:\/\/synacek.org\/code\/projects\/mileus-mobile\/","content":"<p>The goal of the applications was to provide two mobile apps - one for end-users (customers) and other for service providers (taxi drivers).<\/p>\n<div class=\"row wrap\">\n  \n\n\n  \n\n\n<div class=\" image\">\n  <wc-lightbox>\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-taxi-0.png\"\n        loading=\"lazy\"\n        alt=\"Promotional picture of Mileus app showing taxi in a city.\"\n        width=\"160px\"\n        height=\"auto;\"\n      \/>\n      \n      \n    \n  <\/wc-lightbox>\n<\/div>\n\n  \n\n\n  \n\n\n<div class=\" image\">\n  <wc-lightbox>\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-taxi-1.png\"\n        loading=\"lazy\"\n        alt=\"Promotional picture of Mileus app showing screen with a map with line between home and work locations.\"\n        width=\"160px\"\n        height=\"auto;\"\n      \/>\n      \n      \n    \n  <\/wc-lightbox>\n<\/div>\n\n  \n\n\n  \n\n\n<div class=\" image\">\n  <wc-lightbox>\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-taxi-2.png\"\n        loading=\"lazy\"\n        alt=\"Promotional picture of Mileus app showing screen with parameters of journey.\"\n        width=\"160px\"\n        height=\"auto;\"\n      \/>\n      \n      \n    \n  <\/wc-lightbox>\n<\/div>\n\n  \n\n\n  \n\n\n<div class=\" image\">\n  <wc-lightbox>\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-taxi-3.png\"\n        loading=\"lazy\"\n        alt=\"Promotional picture of Mileus app showing screen with parameters of journey.\"\n        width=\"160px\"\n        height=\"auto;\"\n      \/>\n      \n      \n    \n  <\/wc-lightbox>\n<\/div>\n\n  \n\n\n  \n\n\n<div class=\" image\">\n  <wc-lightbox>\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-taxi-4.png\"\n        loading=\"lazy\"\n        alt=\"Promotional picture of Mileus app showing screen with parameters of a service subscription.\"\n        width=\"160px\"\n        height=\"auto;\"\n      \/>\n      \n      \n    \n  <\/wc-lightbox>\n<\/div>\n\n  \n\n\n  \n\n\n<div class=\" image\">\n  <wc-lightbox>\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-taxi-5.png\"\n        loading=\"lazy\"\n        alt=\"Promotional picture of Mileus app showing screen with map and navigation.\"\n        width=\"160px\"\n        height=\"auto;\"\n      \/>\n      \n      \n    \n  <\/wc-lightbox>\n<\/div>\n\n  \n\n\n  \n\n\n<div class=\" image\">\n  <wc-lightbox>\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-taxi-6.png\"\n        loading=\"lazy\"\n        alt=\"Promotional picture of Mileus service with illustration of taxi and subway.\"\n        width=\"160px\"\n        height=\"auto;\"\n      \/>\n      \n      \n    \n  <\/wc-lightbox>\n<\/div>\n\n  \n\n\n  \n\n\n<div class=\" image\">\n  <wc-lightbox>\n    \n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-taxi-7.png\"\n        loading=\"lazy\"\n        alt=\"Promotional picture of Mileus service with illustration of taxi and subway amongst buildings.\"\n        width=\"160px\"\n        height=\"auto;\"\n      \/>\n      \n      \n    \n  <\/wc-lightbox>\n<\/div>\n\n<\/div>\n<p>Requirement for applications were:<\/p>\n<ul>\n<li>multi-platform: iOS &amp; Android<\/li>\n<li>location-aware: GPS sensors<\/li>\n<li>ability to work offline with limited capability<\/li>\n<li>purchase of recurring subscription via the device using a payment system<\/li>\n<li>multilingual: detect device's locale and provide English and Czech translations<\/li>\n<\/ul>\n<p>When different technologies were evaluated, <em>Flutter<\/em> framework came out as a winner because it could fulfill these requirements.<\/p>\n<p>Both applications are very much like what other ride-hailing services, such as Uber offer.<\/p>\n\n\n\n  \n\n\n<div class=\"centered video\">\n  \n  <figure>\n  \n    <video\n     controls=\"\"\n     muted=\"\"\n     title=\"Commute flow\"\n     src=\"&#x2F;video&#x2F;mileus-taxi-commute.mov\"\n     class=\"\"\n     width=\"400px\"\n     height=\"auto\"\n    >\n    <\/video>\n    \n      <figcaption>\n        The commute flow within the customer application. User searches for available taxis, starts the commute. The taxi catches up with the commuter later on.\n      <\/figcaption>\n    \n  \n  <\/figure>\n  \n<\/div>\n<p>Customer application allows users to sign up for the service. The purchase flow will ask users to fill-in their home and destination addresses, calculate the recommended route and the subscription price. The price is tailored specifically for each customer, depending on their journey.<\/p>\n\n\n\n  \n\n\n<div class=\"centered video\">\n  \n  <figure>\n  \n    <video\n     controls=\"\"\n     muted=\"\"\n     title=\"Subscription flow\"\n     src=\"&#x2F;video&#x2F;mileus-taxi-subscription.mov\"\n     class=\"\"\n     width=\"400px\"\n     height=\"auto\"\n    >\n    <\/video>\n    \n      <figcaption>\n        User has to subscribe to a commute from theirwork address to home. App requires both addresses, user is provided with recommended routes and finally pays for the subscription.\n      <\/figcaption>\n    \n  \n  <\/figure>\n  \n<\/div>\n<p>Customer app needs device location in order to navigate the user. When commute is activated, the service will pair commuter with a driver. From then on, the app helps commuter with navigation via map and commute directions.<\/p>\n<p>The application can work offline most of the time as it needs to work reliably when user is in a subway with no cell signal.<\/p>\n<p>Driver application connects drivers with commuters. Driver can sign-in to predefined shift. When the shift is started, driver can receive orders. Application will then pair driver with commuter with the help of back-end service that can process the state of the taxi fleet and commuter demand.<\/p>\n<p>Both applications require near real-time communcation with backend infrastructure. We relied on Firebase (FireStore) service to serve as an update mechanism, for example when new order arrived, it would trigger UI within the driver app. Analogically, if user started commute and driver was matched, the FireStore would receive new record and that would be reflected in UI as well.\nFirebase was also used for push notifications.<\/p>\n<p>The development time for both apps was less than a year, me being sole developer for 90% of the project in regards to mobile applications.<\/p>\n<p>This meant that I chose certain approaches. Such as creating external library that dealt with shared code between two applications. This library contained shared logic (utilities for time conversion), classes (storage &amp; API calls) or even whole UI flows (user registration).<\/p>\n<p>I even developed <a href=\"https:\/\/github.com\/comatory\/urldat\">urldat<\/a> to help me with contructing API URLs.<\/p>\n<p>We needed to have solid mapping solution, so I went with <em>flutter_map<\/em> library and I ended up <a href=\"https:\/\/github.com\/fleaflet\/flutter_map\/pulls?q=is%3Apr+author%3Acomatory+is%3Aclosed\">contributing to the project<\/a> in order to fix some deficiencies.<\/p>\n<p>One of the things I wanted to get right out of the gate was to have software architecture that would allow to decouple part of the applications.<\/p>\n<p>First step was to strictly separate UI (widgets) and logic such as data fetching or manipulation with the map. I leveraged library <em>flutter_bloc<\/em> that allowed me to do two things:<\/p>\n<ul>\n<li>manage global application state<\/li>\n<li>extract logic into units (BLoCs) that are not part of UI<\/li>\n<\/ul>\n<p>The communication between widgets and BLoCs was made via widgets that would re-render whenever state was changed.<\/p>\n\n\n\n  \n\n\n<div class=\"centered video\">\n  \n  <figure>\n  \n    <video\n     controls=\"\"\n     muted=\"\"\n     title=\"Debugging UI\"\n     src=\"&#x2F;video&#x2F;mileus-taxi-debug.mov\"\n     class=\"\"\n     width=\"400px\"\n     height=\"auto\"\n    >\n    <\/video>\n    \n      <figcaption>\n        Debugging UI to observe current state of sensors, Firebase services and user account. It is possible to see application logs or save these logs into a file.\n      <\/figcaption>\n    \n  \n  <\/figure>\n  \n<\/div>\n<p>Technical implementations such as data fetching over network (API), writing and reading from device disk, encryption - these were extracted into modules and classes. I used dependency injection so these modules could communicate between each other without being coupled together.\nThis solution proved to be good since later on in the project, another developer joined the team and the changes could be made to specific isolated parts of the codebase. It was easier to review changes and integrate the work efficiently.<\/p>\n<div class=\"row wrap centered\">\n    \n\n\n  \n\n\n<div class=\" video\">\n  \n  <figure>\n  \n    <video\n     controls=\"\"\n     muted=\"\"\n     title=\"Debugging UI\"\n     src=\"&#x2F;video&#x2F;mileus-taxi-onboarding.mov\"\n     class=\"\"\n     width=\"300px\"\n     height=\"auto\"\n    >\n    <\/video>\n    \n      <figcaption>\n        User onboarding.\n      <\/figcaption>\n    \n  \n  <\/figure>\n  \n<\/div>\n\n    \n\n\n  \n\n\n<div class=\" video\">\n  \n  <figure>\n  \n    <video\n     controls=\"\"\n     muted=\"\"\n     title=\"Debugging UI\"\n     src=\"&#x2F;video&#x2F;mileus-taxi-registration.mov\"\n     class=\"\"\n     width=\"300px\"\n     height=\"auto\"\n    >\n    <\/video>\n    \n      <figcaption>\n        User registration.\n      <\/figcaption>\n    \n  \n  <\/figure>\n  \n<\/div>\n\n<\/div>\n"},{"title":"mileus\/ui","published":"2021-11-01T00:00:00+00:00","updated":"2021-11-01T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/projects\/mileus-ui\/"}},"id":"https:\/\/synacek.org\/code\/projects\/mileus-ui\/","content":"<p>I decided to build React component library for Mileus' web projects because we were building multiple web applications based on same stack.<\/p>\n<p>It was important to have consistent look across them and I also wanted to be more productive by working on business requirements instead of reinventing basic UI components each time. The result was this library that was distributed as NPM package.<\/p>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <picture>\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-input.webp\" type=\"image&#x2F;webp\">\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-input.gif\" type=\"image&#x2F;gif\">\n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-ui-input.gif\"\n        loading=\"lazy\"\n        alt=\"Text input componentText input component with validation.\"\n        width=\"320px\"\n        height=\"auto;\"\n      \/>\n      \n      <\/picture>\n      \n      \n        <figcaption>\n          Text input component with validation.\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <picture>\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-toggle.webp\" type=\"image&#x2F;webp\">\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-toggle.gif\" type=\"image&#x2F;gif\">\n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-ui-toggle.gif\"\n        loading=\"lazy\"\n        alt=\"Button toggle componentToggle button component composed from base buttons.\"\n        width=\"320px\"\n        height=\"auto;\"\n      \/>\n      \n      <\/picture>\n      \n      \n        <figcaption>\n          Toggle button component composed from base buttons.\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <picture>\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-datetime.webp\" type=\"image&#x2F;webp\">\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-datetime.gif\" type=\"image&#x2F;gif\">\n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-ui-datetime.gif\"\n        loading=\"lazy\"\n        alt=\"Datetime input componentCombined date time input component which is locale aware.\"\n        width=\"320px\"\n        height=\"auto;\"\n      \/>\n      \n      <\/picture>\n      \n      \n        <figcaption>\n          Combined date time input component which is locale aware.\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n<p>The library is heavily opinionated which means it comes with its own stylesheet and typography to maintain brand identity.<\/p>\n<p>These are just some components that the library uses:<\/p>\n<ul>\n<li>accordion header<\/li>\n<li>autocomplete combobox that fetches options dynamically<\/li>\n<li>buttons<\/li>\n<li>date and time input that can be combined to enter both<\/li>\n<li>icon set<\/li>\n<li>configurable tooltip based on PopperJS<\/li>\n<\/ul>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <picture>\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-tooltip.webp\" type=\"image&#x2F;webp\">\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-tooltip.gif\" type=\"image&#x2F;gif\">\n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-ui-tooltip.gif\"\n        loading=\"lazy\"\n        alt=\"Tooltip componentTooltip applied to button, triggers on hover or focus. Tooltip can automatically switch its location if it detects window boundaries.\"\n        width=\"320px\"\n        height=\"auto;\"\n      \/>\n      \n      <\/picture>\n      \n      \n        <figcaption>\n          Tooltip applied to button, triggers on hover or focus. Tooltip can automatically switch its location if it detects window boundaries.\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <picture>\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-combobox.webp\" type=\"image&#x2F;webp\">\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-combobox.gif\" type=\"image&#x2F;gif\">\n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-ui-combobox.gif\"\n        loading=\"lazy\"\n        alt=\"Address comboboxAddress combobox component which automatically fetches options from server. It uses portal so option list is always visible even when the input is placed in container with hidden overflow.\"\n        width=\"320px\"\n        height=\"auto;\"\n      \/>\n      \n      <\/picture>\n      \n      \n        <figcaption>\n          Address combobox component which automatically fetches options from server. It uses portal so option list is always visible even when the input is placed in container with hidden overflow.\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n\n\n\n  \n\n\n<div class=\"centered image\">\n  <wc-lightbox>\n    \n    <figure style=\"\">\n    \n      \n      <picture>\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-icons.webp\" type=\"image&#x2F;webp\">\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-icons.gif\" type=\"image&#x2F;gif\">\n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-ui-icons.gif\"\n        loading=\"lazy\"\n        alt=\"IconsIcons specific to Mileus&#x27; business domain.\"\n        width=\"320px\"\n        height=\"auto;\"\n      \/>\n      \n      <\/picture>\n      \n      \n        <figcaption>\n          Icons specific to Mileus&#x27; business domain.\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n<p>Apart from React components the library also exports series of CSS variables used for consistency such as<\/p>\n<ul>\n<li>responsive breakpoints<\/li>\n<li>margin and padding<\/li>\n<li>colors<\/li>\n<li>typographical sizes and weights<\/li>\n<\/ul>\n<div class='row--centered'>\n    \n\n\n  \n\n\n<div class=\" image\">\n  <wc-lightbox>\n    \n    <figure style=\"width:160px\">\n    \n      \n      <picture>\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-colors.webp\" type=\"image&#x2F;webp\">\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-colors.png\" type=\"image&#x2F;png\">\n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-ui-colors.png\"\n        loading=\"lazy\"\n        alt=\"ColorsColors used for components and exposed as CSS variables\"\n        width=\"160px\"\n        height=\"auto;\"\n      \/>\n      \n      <\/picture>\n      \n      \n        <figcaption>\n          Colors used for components and exposed as CSS variables\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n\n    \n\n\n  \n\n\n<div class=\" image\">\n  <wc-lightbox>\n    \n    <figure style=\"width:160px\">\n    \n      \n      <picture>\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-spacing.webp\" type=\"image&#x2F;webp\">\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-spacing.png\" type=\"image&#x2F;png\">\n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-ui-spacing.png\"\n        loading=\"lazy\"\n        alt=\"SpacingSet of CSS variables for margin and padding used for components. These are also exposed and can be used to implement consistent spacing - inspired by Tailwind CSS.\"\n        width=\"160px\"\n        height=\"auto;\"\n      \/>\n      \n      <\/picture>\n      \n      \n        <figcaption>\n          Set of CSS variables for margin and padding used for components. These are also exposed and can be used to implement consistent spacing - inspired by Tailwind CSS.\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n\n    \n\n\n  \n\n\n<div class=\" image\">\n  <wc-lightbox>\n    \n    <figure style=\"width:160px\">\n    \n      \n      <picture>\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-typography.webp\" type=\"image&#x2F;webp\">\n        <source srcset=\"&#x2F;image&#x2F;mileus-ui-typography.png\" type=\"image&#x2F;png\">\n      \n      <img\n        src=\"&#x2F;image&#x2F;mileus-ui-typography.png\"\n        loading=\"lazy\"\n        alt=\"TypographyTypography used for components and exposed CSS variables for sizes and weights. Used font is a variable font so supported browsers save bandwith.\"\n        width=\"160px\"\n        height=\"auto;\"\n      \/>\n      \n      <\/picture>\n      \n      \n        <figcaption>\n          Typography used for components and exposed CSS variables for sizes and weights. Used font is a variable font so supported browsers save bandwith.\n        <\/figcaption>\n      \n    \n    <\/figure>\n    \n  <\/wc-lightbox>\n<\/div>\n\n<\/div>\n<p>Thanks to PostCSS it was possible to use breakpoint CSS variables in target projects.<\/p>\n<p>It was very important to me not to have huge size of the library. The library has separate entry point and bundles for date time component as it requires <code>date-fns<\/code> library. This means that you don't pay the upfront cost of downloading it if you're only using button component for example.<\/p>\n<p>All components are accessible via keyboard with correct focus behaviour.<\/p>\n<p>The repository itself also contains Storybook to preview and test the components. This approach turned out to be a good decision since designer could check his work and try out different states of the components.<\/p>\n"},{"title":"try.mileus.com","published":"2021-11-01T00:00:00+00:00","updated":"2021-11-01T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/projects\/try-mileus-com\/"}},"id":"https:\/\/synacek.org\/code\/projects\/try-mileus-com\/","content":"<p>Multilingual web application created with fast-loading in mind. The purpose of the web app is to communicate the upsides of using inter-modal transportation in Prague.<\/p>\n<div class=\"centered\">\n  <figure>\n    <a href=\"\/image\/mileus-web-app.webp\" target=\"_blank\">\n      <picture>\n        <source srcset=\"\/image\/mileus-web-app.webp\" type=\"image\/webp\">\n        <source srcset=\"\/image\/mileus-web-app.gif\" type=\"image\/gif\">\n        <img\n          src=\"\/image\/mileus-web-app.gif\"\n          style=\"width: 320px; height: auto\"\n          loading=\"lazy\"\n          alt=\"Web app user flow\"\n        \/>\n      <\/picture>\n    <\/a>\n    <figcaption>\n      This is what the web app looks like on mobile devices.\n    <\/figcaption>\n  <\/figure>\n<\/div>\n<p>Project was launched in Q2 2021 on domain <a href=\"https:\/\/try.mileus.com\" target=\"_blank\">try.mileus.com<\/a> [<a href=\"https:\/\/web.archive.org\/web\/*\/http:\/\/try.mileus.com\/*\" target=\"_blank\">archive<\/a>] to promote <a href=\"https:\/\/mileus.com\">Mileus'<\/a> subscription commute service.<\/p>\n<p>The emphasis was put to quick asset loading so code-splitting for map page was implemented. The web application contains interactive map that shows the recommended route. Each route can be toggled and inspected in detail.<\/p>\n<p>Part of the project was also building the landing page with custom elements such as accordeons and list item.<\/p>\n<p>Application is responsive and scales well to mobile, tablet and desktop viewports. Some viewports have custom layouts and show what is appropriate for each device.<\/p>\n<div class=\"on-left\">\n  <figure>\n    <a href=\"\/image\/mileus-web-app-map.webp\" target=\"_blank\">\n      <picture>\n        <source srcset=\"\/image\/mileus-web-app-map.webp\" type=\"image\/webp\">\n        <source srcset=\"\/image\/image\/mileus-web-app-map.gif\" type=\"image\/gif\">\n        <img\n          alt=\"Web application user flow\"\n          src=\"\/image\/mileus-web-app-map.gif\"\n          style=\"width: 100%; max-width: 400px\"\n          loading=\"lazy\"\n        \/>\n      <\/picture>\n    <\/a>\n    <figcaption>\n      Interactive map screen in various viewport widths.\n    <\/figcaption>\n  <\/figure>\n<\/div>\n<p>The tech stack was using simple React in combination with Redux for state management. PostCSS was used to serve efficient stylesheets which simply leverage power of CSS variables. The goal was to bring as little dependencies as possible.<\/p>\n<p>My goal was to separate business logic and data-fetching from UI itself. So instead of putting this code into React components I opted for using <a href=\"https:\/\/github.com\/reduxjs\/redux-thunk\" target=\"_blank\">Redux Thunk<\/a> middleware.\nBusiness logic is placed inside regular functions. These functions are invoked by Redux actions from UI.<\/p>\n<p>Responsibility for data fetching, translations, persistence via local storage is done via separating the relevant code in service classes. These classes are instantiated mostly as singletons and passed via dependency injection mechanism into aforementioned Thunk functions. This makes it simple to refactor, replace and test different part of application.<\/p>\n<p>The app has persistence built-in via local storage so when user visits the app again, they do not have to fill-in required data and just continue with their journey.\nSimilar mechanism was used to synchronize state between mutliple tabs to help with user retention.<\/p>\n<p>At the same time this web app was created, I was building in-house React component library which is used here. Inputs, autocompletes, form elements and others were implemented in order to be re-used across other company projects.<\/p>\n"},{"title":"fb2iCal","published":"2021-09-21T00:00:00+00:00","updated":"2021-09-21T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/projects\/fb2ical\/"}},"id":"https:\/\/synacek.org\/code\/projects\/fb2ical\/","content":{"@attributes":{"type":"html"}}},{"title":"Avocode Email Tags Input","published":"2021-08-21T00:00:00+00:00","updated":"2021-08-21T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/projects\/avocode-email-tagsinput\/"}},"id":"https:\/\/synacek.org\/code\/projects\/avocode-email-tagsinput\/","content":{"@attributes":{"type":"html"}}},{"title":"urldat","published":"2021-07-03T00:00:00+00:00","updated":"2021-07-03T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/utilities\/urldat\/"}},"id":"https:\/\/synacek.org\/code\/utilities\/urldat\/","content":{"@attributes":{"type":"html"}}},{"title":"minidotenv","published":"2020-01-25T00:00:00+00:00","updated":"2020-01-25T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/utilities\/minidotenv\/"}},"id":"https:\/\/synacek.org\/code\/utilities\/minidotenv\/","content":{"@attributes":{"type":"html"}}},{"title":"PortionTracker","published":"2018-12-09T00:00:00+00:00","updated":"2018-12-09T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/projects\/portion-tracker\/"}},"id":"https:\/\/synacek.org\/code\/projects\/portion-tracker\/","content":{"@attributes":{"type":"html"}}},{"title":"proximity-search-array","published":"2017-11-11T00:00:00+00:00","updated":"2017-11-11T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/utilities\/proximity-search-array\/"}},"id":"https:\/\/synacek.org\/code\/utilities\/proximity-search-array\/","content":{"@attributes":{"type":"html"}}},{"title":"es6-frontend-boilerplate-2017","published":"2017-07-18T00:00:00+00:00","updated":"2017-07-18T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/utilities\/es6-frontend-boilerplate-2017\/"}},"id":"https:\/\/synacek.org\/code\/utilities\/es6-frontend-boilerplate-2017\/","content":{"@attributes":{"type":"html"}}},{"title":"Rotation Zoomer","published":"2016-12-27T00:00:00+00:00","updated":"2016-12-27T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/projects\/rotation-zoomer\/"}},"id":"https:\/\/synacek.org\/code\/projects\/rotation-zoomer\/","content":{"@attributes":{"type":"html"}}},{"title":"Dignus","published":"2015-11-19T00:00:00+00:00","updated":"2015-11-19T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/projects\/dignus\/"}},"id":"https:\/\/synacek.org\/code\/projects\/dignus\/","content":{"@attributes":{"type":"html"}}},{"title":"oldGamesScraper","published":"2015-10-06T00:00:00+00:00","updated":"2015-10-06T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/utilities\/oldgamesscraper\/"}},"id":"https:\/\/synacek.org\/code\/utilities\/oldgamesscraper\/","content":{"@attributes":{"type":"html"}}},{"title":"DuckDuckGo Instant Answers","published":"2015-09-18T00:00:00+00:00","updated":"2015-09-18T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/projects\/duckduckgo-instant-answers\/"}},"id":"https:\/\/synacek.org\/code\/projects\/duckduckgo-instant-answers\/","content":{"@attributes":{"type":"html"}}},{"title":"Sky To Speak website","published":"2013-10-21T00:00:00+00:00","updated":"2013-10-21T00:00:00+00:00","author":{"name":"\n            \n              Unknown\n            \n          "},"link":{"@attributes":{"rel":"alternate","type":"text\/html","href":"https:\/\/synacek.org\/code\/projects\/sky-to-speak-web\/"}},"id":"https:\/\/synacek.org\/code\/projects\/sky-to-speak-web\/","content":{"@attributes":{"type":"html"}}}]}