<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: nop33</title>
    <description>The latest articles on DEV Community by nop33 (@nop33).</description>
    <link>https://dev.to/nop33</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F62099%2F4c7f8a2a-87e0-4af4-abaf-7e78300c2ac5.png</url>
      <title>DEV Community: nop33</title>
      <link>https://dev.to/nop33</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nop33"/>
    <language>en</language>
    <item>
      <title>How to Combine TanStack Query Data in React</title>
      <dc:creator>nop33</dc:creator>
      <pubDate>Sat, 01 Aug 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/nop33/how-to-combine-tanstack-query-data-in-react-1bei</link>
      <guid>https://dev.to/nop33/how-to-combine-tanstack-query-data-in-react-1bei</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Who this is for.&lt;/strong&gt; You already use TanStack Query and are comfortable with &lt;code&gt;useQuery&lt;/code&gt; and query keys. If you are not there yet, read &lt;a href="https://tkdodo.eu/blog/practical-react-query" rel="noopener noreferrer"&gt;TkDodo’s React Query series&lt;/a&gt; first. It is the best writing there is on this library, and everything below assumes it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One common TanStack Query pattern is that you can create your own wrapper hooks around the primitive hooks the library provides, especially if you want to reuse them across several components or other hooks, or just want to keep your components’ code clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/Asset.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Asset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;assetId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAssetPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// src/api/queries.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useAssetPrice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;priceQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;priceQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;queryOptions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prices&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchPrices&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TkDodo recommends exactly this in &lt;a href="https://tkdodo.eu/blog/practical-react-query#create-custom-hooks" rel="noopener noreferrer"&gt;Practical React Query&lt;/a&gt;, and it is good advice. The query key and the fetcher live in one place. The component doesn’t need to know how the data arrives, it just uses the data it needs.&lt;/p&gt;

&lt;p&gt;An interesting question then is: What goes &lt;strong&gt;inside&lt;/strong&gt; these wrapper hooks?&lt;/p&gt;

&lt;p&gt;For an example like the one above where the data the component needs comes directly from a single API endpoint, the answer is simple: just the &lt;code&gt;useQuery&lt;/code&gt; call. But what if the shape of the data the component needs has to &lt;strong&gt;be derived by combining data&lt;/strong&gt; from multiple API endpoints?&lt;/p&gt;

&lt;p&gt;To illustrate what I mean: Take the example of a portfolio app that supports multiple accounts and displays the total value. You create &lt;code&gt;usePortfolioValue()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The endpoints you have available for deriving the value are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// get assets list of 1 account&lt;/span&gt;
&lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;accounts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;assets&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;

&lt;span class="c1"&gt;// get prices of all assets from an external market-data service&lt;/span&gt;
&lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;prices&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How would you do it?&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://tkdodo.eu/blog/react-query-data-transformations" rel="noopener noreferrer"&gt;detailed catalogue of options for transforming data&lt;/a&gt; already exists. Combining is a different problem, and only partially addressed online. If your backend can do the &lt;code&gt;JOIN&lt;/code&gt; for you, take it. But if it can’t, keep reading.&lt;/p&gt;

&lt;p&gt;You will see this called different things depending on where you read about it. Derived queries. Query composition. Combining queries. Same question.&lt;/p&gt;

&lt;p&gt;In this article I am exploring different ways the community and myself have thought of to solve this problem, each with its own trade-offs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Spoiler alert&lt;/em&gt;: There is no one best solution, but a solution listed here might help you in your particular situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Back to the portfolio example. The user connects several accounts, let’s say 3 accounts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stocks account&lt;/li&gt;
&lt;li&gt;precious metals account&lt;/li&gt;
&lt;li&gt;crypto account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each account holds several assets. The overview page looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total value
$48,150.00                                 

Asset Allocation % Value
───────────────────────────────────────────
AAPL ██████░░░░░░░░░░░░░░ 32% $15,408
VWRL █████░░░░░░░░░░░░░░░ 24% $11,556
BTC  ████░░░░░░░░░░░░░░░░ 18% $8,667
MSFT ███░░░░░░░░░░░░░░░░░ 16% $7,704
GOLD ██░░░░░░░░░░░░░░░░░░ 10% $4,815
───────────────────────────────────────────
                              100% $48,150
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given the 2 available &lt;code&gt;GET&lt;/code&gt; endpoints mentioned above, let’s try to derive the total portfolio value.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Combine in the component
&lt;/h2&gt;

&lt;p&gt;The most obvious one is to combine the data in the render function of the component, the body of our wrapper hook, by calling &lt;code&gt;useQuery&lt;/code&gt;/&lt;code&gt;useQueries&lt;/code&gt; for each piece of data and stitching them together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usePortfolioValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAccountIds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prices&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;pricesQuery&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;assets&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQueries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accountIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assetsQuery&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;combine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mergeAssets&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
    &lt;span class="nf"&gt;reduceTotalValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// module scope, so the reference stays stable&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mergeAssets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assetsPerAccounts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
  &lt;span class="nx"&gt;assetsPerAccounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🤔 Aside&lt;br&gt;
You might object and say &lt;em&gt;“Hey, you are also combining in &lt;code&gt;useQueries&lt;/code&gt;, you’re not just combining them in the &lt;code&gt;useMemo()&lt;/code&gt; call”&lt;/em&gt;. And you would be right. This is &lt;strong&gt;extremely&lt;/strong&gt; necessary.&lt;/p&gt;

&lt;p&gt;First of all, why &lt;code&gt;useQueries&lt;/code&gt;? Because the number of accounts is not known ahead of time, and you can’t use &lt;code&gt;useQuery&lt;/code&gt; in a loop. Secondly, without &lt;code&gt;combine&lt;/code&gt; the results the &lt;code&gt;useQueries&lt;/code&gt; call spits out will be a freshly built array. On every render. This means the &lt;code&gt;useMemo()&lt;/code&gt; deps would change every time. Your &lt;code&gt;reduceTotalValue()&lt;/code&gt; function then runs on every render, defeating the point of using &lt;code&gt;useMemo()&lt;/code&gt;. So, use &lt;code&gt;combine&lt;/code&gt; when using &lt;code&gt;useQueries&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a totally valid approach. It is important, however, to understand &lt;a href="https://tkdodo.eu/blog/inside-react-query" rel="noopener noreferrer"&gt;what happens under the hood&lt;/a&gt;. Let’s do some arithmetic. Our &lt;code&gt;usePortfolioValue()&lt;/code&gt; hook will create several Observers once it’s called, one for each Query. One for the prices data, and one for each account data. In our example of 3 accounts, that is &lt;strong&gt;4 Observers per consumer&lt;/strong&gt; , one for prices and one for each account, for every use of &lt;code&gt;usePortfolioValue()&lt;/code&gt; across our app.&lt;/p&gt;

&lt;p&gt;Another thing worth noticing is what happens when our cached data changes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If prices change, the prices Observer notifies the component and it rerenders. The &lt;code&gt;mergeAssets()&lt;/code&gt; function, however, won’t run. It lives in a &lt;em&gt;different&lt;/em&gt; Observer, the one watching the accounts, and none of the queries that Observer watches has changed. &lt;code&gt;combine&lt;/code&gt; is memoized per Observer, so it hands back the previous result untouched. The &lt;code&gt;useMemo()&lt;/code&gt; deps do change, of course, so we get a new portfolio value.&lt;/li&gt;
&lt;li&gt;If an account’s data change, the corresponding Observer will run the &lt;code&gt;combine&lt;/code&gt; method (our &lt;code&gt;mergeAssets()&lt;/code&gt;) which will return new results. &lt;code&gt;useMemo()&lt;/code&gt; runs again and our components rerender.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Combine in &lt;code&gt;combine&lt;/code&gt; of &lt;code&gt;useQueries&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Did you notice how useful that &lt;code&gt;combine&lt;/code&gt; memoization is? This next approach hands &lt;em&gt;all&lt;/em&gt; the queries to one &lt;code&gt;useQueries&lt;/code&gt; call, so the whole derivation gets it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usePortfolioValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAccountIds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQueries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;accountIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assetsQuery&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;pricesQuery&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;combine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;combineResults&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// prices was listed last, so everything before it is an account&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;combineResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assetsPerAccounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reduceTotalValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mergeAssets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assetsPerAccounts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;reduceTotalValue()&lt;/code&gt; and &lt;code&gt;mergeAssets()&lt;/code&gt; stay the same as before, nothing new here about them. What is new is that &lt;code&gt;reduceTotalValue()&lt;/code&gt; is no longer called at the component/hook render function level but rather in the Observer level, just like our &lt;code&gt;mergeAssets()&lt;/code&gt; from before. That means the result is memoized, and the output of &lt;code&gt;combine&lt;/code&gt; runs through &lt;a href="https://tanstack.com/query/v5/docs/framework/react/guides/render-optimizations#structural-sharing" rel="noopener noreferrer"&gt;structural sharing&lt;/a&gt; before it reaches the consumer, so the components rerender only when the portfolio value actually changes. If, for example, prices of two assets change together and they cancel each other out, the components won’t rerender. I wouldn’t judge you if you think this is a stupid example. But at least it demonstrates my point.&lt;/p&gt;

&lt;p&gt;Did you notice something, though? What happens now if only 1 price changes? The Observer will run &lt;code&gt;combineResults()&lt;/code&gt; which itself will run &lt;code&gt;mergeAssets()&lt;/code&gt; and the new portfolio value is returned. In our previous approach, &lt;code&gt;mergeAssets()&lt;/code&gt; didn’t need to run. The assets didn’t change, only a price changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You traded one optimization for another.&lt;/strong&gt; If your &lt;code&gt;mergeAssets()&lt;/code&gt; is the expensive one, prefer the first approach. If your &lt;code&gt;reduceTotalValue()&lt;/code&gt; is the expensive one, prefer the latter one.&lt;/p&gt;

&lt;p&gt;(&lt;em&gt;I’d add that you’re also trading some code readability.&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;As for the number of Observers, nothing changed. Still &lt;strong&gt;4 Observers per consumer&lt;/strong&gt;. Same queries, just combined in a different place.&lt;/p&gt;




&lt;p&gt;You are happy with your &lt;code&gt;usePortfolioValue()&lt;/code&gt; hook, you pat yourself on the back, and you start using it around in your codebase. In fact, you realize you need to calculate the allocation percentage for each asset row, so you decide to use your new hook on each asset row. Now, &lt;code&gt;usePortfolioValue()&lt;/code&gt; has 6 consumers, the header value, and each row.&lt;/p&gt;

&lt;p&gt;(&lt;em&gt;If you think this example is not realistic and that the developer should instead prop-drill the value or put it in a Context, you have a point. That is a discussion of its own and I cover it in a follow-up. Here it is just a way to illustrate the other approaches.&lt;/em&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total value
$48,150.00 &amp;lt;- usePortfolioValue()

Asset Allocation % Value
───────────────────────────────────────────
AAPL ██████░░░░░░░░░░░░░░ 32% $15,408 &amp;lt;- usePortfolioValue()
VWRL █████░░░░░░░░░░░░░░░ 24% $11,556 &amp;lt;- usePortfolioValue()
BTC  ████░░░░░░░░░░░░░░░░ 18% $8,667 &amp;lt;- usePortfolioValue()
MSFT ███░░░░░░░░░░░░░░░░░ 16% $7,704 &amp;lt;- usePortfolioValue()
GOLD ██░░░░░░░░░░░░░░░░░░ 10% $4,815 &amp;lt;- usePortfolioValue()
───────────────────────────────────────────
                              100% $48,150
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You think to yourself, &lt;em&gt;“hmm… so much wasted computation. Every time I use &lt;code&gt;usePortfolioValue()&lt;/code&gt;, the results of &lt;code&gt;reduceTotalValue()&lt;/code&gt; and &lt;code&gt;mergeAssets()&lt;/code&gt; need to be recalculated.”&lt;/em&gt; And you would be correct. No matter which of the above approaches you use, the results are cached either at the component level (in &lt;code&gt;useMemo()&lt;/code&gt;) or in the Observer level (in &lt;code&gt;combine&lt;/code&gt;). The results are cached &lt;strong&gt;per consumer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are several ideas on how to share a cache between consumers. &lt;a href="https://tkdodo.eu/blog/react-query-selectors-supercharged#the-final-boss" rel="noopener noreferrer"&gt;One idea is to move the memoization outside of the components and the Observers&lt;/a&gt; completely by using a separate library like &lt;a href="https://github.com/caiogondim/fast-memoize.js" rel="noopener noreferrer"&gt;fast-memoize.js&lt;/a&gt;. There are, however, a few more ideas that don’t require an additional package, which you might find useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Combine in &lt;code&gt;queryFn&lt;/code&gt; with &lt;code&gt;Promise.all&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;One of these ideas is to move the derived value cache one level deeper. In the QueryCache itself. Who controls what goes in the QueryCache? The &lt;code&gt;queryFn&lt;/code&gt; function of a Query. So &lt;a href="https://github.com/TanStack/query/discussions/764#discussioncomment-44804" rel="noopener noreferrer"&gt;here’s a take on it&lt;/a&gt;, using &lt;code&gt;Promise.all()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usePortfolioValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAccountIds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;portfolioQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountIds&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;portfolioQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountIds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;queryOptions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;portfolio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accountIds&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assetsPromises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetchAccountAssets&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;assetsPerAccounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="nx"&gt;assetsPromises&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;fetchPrices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;])&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reduceTotalValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assetsPerAccounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The derivation of the portfolio value now happens at the Query level and stored in the QueryCache. It is calculated on every refetch. It is not calculated per consumer anymore. When all 6 of the above components mount, the calculation will run only once. Your components now have a cache they can share. But at what cost?&lt;/p&gt;

&lt;p&gt;Since the QueryCache now stores only the derived value, any component that simply needs the price of an asset needs to fetch it again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prices&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;pricesQuery&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So one cost of a shared derived cache is additional network requests. You’ve lost granularity.&lt;/p&gt;

&lt;p&gt;Another cost is error handling. I purposefully left loading and error states out of scope to keep the article content simple. But it’s worth talking about it here. With the above &lt;code&gt;Promise.all()&lt;/code&gt; implementation, if &lt;em&gt;any&lt;/em&gt; of the API calls returns an error, the whole Query throws and will be retried again, depending on your configuration. Imagine an extreme scenario where your app has 100 accounts, not 3. Fire 101 requests at the same time and you will most probably hit some rate limiting and &lt;code&gt;429&lt;/code&gt;s. Even if only 1 out of those 101 returns a &lt;code&gt;429&lt;/code&gt;, all 101 requests will then be retried.&lt;/p&gt;

&lt;p&gt;This again, is a trade-off against additional network requests, and an even more dangerous one that you need to watch out for. You could, of course, reach for &lt;code&gt;Promise.allSettled()&lt;/code&gt; but that wouldn’t solve your retry problem, unless you re-implement retries within the &lt;code&gt;queryFn&lt;/code&gt; but let’s not get there.&lt;/p&gt;

&lt;p&gt;Last thing worth mentioning: you are now down to &lt;strong&gt;1 Observer per consumer&lt;/strong&gt; , from 4. The whole fan-out moved inside the &lt;code&gt;queryFn&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Combine in &lt;code&gt;queryFn&lt;/code&gt; with &lt;code&gt;fetchQuery&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This loss of granularity really bugs you. But then you remember: The &lt;code&gt;queryFn&lt;/code&gt; doesn’t care what’s inside as long as it’s a &lt;code&gt;Promise&lt;/code&gt;. API calls return promises, of course, but so do &lt;code&gt;queryClient.fetchQuery&lt;/code&gt; calls. What if we replaced our raw API calls from the example above with calls to the QueryCache?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usePortfolioValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAccountIds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;portfolioQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountIds&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;portfolioQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountIds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;queryOptions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;portfolio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accountIds&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assetsPromises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;accountIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;assetsQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="nx"&gt;assetsPromises&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;pricesQuery&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;])&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reduceTotalValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, look at that. With only a two-line change, each account keeps its own cache. Prices too. So we can still make the below calls anywhere in our app and we will hit the same cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;assetsQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;pricesQuery&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on &lt;code&gt;staleTime&lt;/code&gt;, &lt;code&gt;fetchQuery&lt;/code&gt; returns the cached value for everything still fresh and only fetches what was invalidated. And the composed result gets an entry too, computed once and read by everyone. I felt very proud when I came up with this idea, and very happy when I later discovered that &lt;a href="https://github.com/TanStack/query/discussions/2178#discussioncomment-3615704" rel="noopener noreferrer"&gt;TkDodo pointed at this solution as well&lt;/a&gt;, in a discussion titled, aptly, &lt;em&gt;Derived Queries&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;You must realize though what you have just traded, once again. A derived entry in the QueryCache is a snapshot. Not a subscription. If the prices query is invalidated, no consumer of &lt;code&gt;usePortfolioValue()&lt;/code&gt; will re-render. The &lt;code&gt;portfolioQuery&lt;/code&gt; has not been invalidated. Query invalidation for the portfolio value is now on your hands. I’ll save you the Uncle Ben quote here.&lt;/p&gt;

&lt;p&gt;Nevertheless, it can be useful. If your app is &lt;a href="https://tkdodo.eu/blog/react-query-as-a-state-manager#smart-refetches" rel="noopener noreferrer"&gt;built around manual invalidations&lt;/a&gt;, this can be a totally valid approach. In an &lt;a href="https://www.nop33.com/portfolio/alephium-desktop-wallet/" rel="noopener noreferrer"&gt;app I’ve worked on&lt;/a&gt; I use a &lt;a href="https://www.nop33.com/blog/tanstack-query-change-detection-polling/" rel="noopener noreferrer"&gt;“change detection” query&lt;/a&gt; that asks the server “are there new data?” and invalidates several queries only when the answer is yes.&lt;/p&gt;

&lt;p&gt;And like the previous approach, you are down to &lt;strong&gt;1 Observer per consumer&lt;/strong&gt;. Unlike it, each source keeps its own cache entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one to reach for
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Observers per consumer&lt;/th&gt;
&lt;th&gt;Derivation runs&lt;/th&gt;
&lt;th&gt;What you give up&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Combine in the component&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;once per consumer&lt;/td&gt;
&lt;td&gt;nothing shared between consumers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Combine in &lt;code&gt;combine&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;once per consumer&lt;/td&gt;
&lt;td&gt;any query data change re-runs the merge too&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. &lt;code&gt;Promise.all&lt;/code&gt; in &lt;code&gt;queryFn&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;once, shared&lt;/td&gt;
&lt;td&gt;granular caches, and one failure retries everything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. &lt;code&gt;fetchQuery&lt;/code&gt; in &lt;code&gt;queryFn&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;once, shared&lt;/td&gt;
&lt;td&gt;you own invalidation of the derived entry&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Reading it as a decision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Few consumers?&lt;/strong&gt; Stop at approach 1 or 2. The sharing the others buy you is not worth the trade. Pick between them by which function is the expensive one, &lt;code&gt;mergeAssets()&lt;/code&gt; or &lt;code&gt;reduceTotalValue()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Many consumers, and nothing else in the app needs the raw sources?&lt;/strong&gt; Approach 3 is the simplest thing that shares the work. Watch the error handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Many consumers, and other screens do need the sources?&lt;/strong&gt; Approach 4. Pay for it by invalidating the derived entry yourself. Or use an external library, which comes with its own trade-offs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these is the right answer in general. They are four different answers to &lt;em&gt;“where should this value be computed and cached”&lt;/em&gt;. Each of your use-cases can have a different answer. You are not restricted to using only one of them.&lt;/p&gt;

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

&lt;p&gt;All above approaches are different ideas of how to implement your &lt;code&gt;useQuery&lt;/code&gt;/&lt;code&gt;useQueries&lt;/code&gt; hook wrappers. We started simple, by deriving and memoizing the data in the render functions, moving to memoizing them in the Observer level, and finally bringing them inside the QueryCache so that all consumers can have access to the same derived data. There are some things though that I alluded to but haven’t talked about.&lt;/p&gt;

&lt;p&gt;I keep counting Observers and I never said why it matters. That turns out to be an article of its own, because it is really a question about where you call these hooks rather than how you write them.&lt;/p&gt;

&lt;p&gt;TanStack Query encourages you to call them wherever you need the data. That advice is good, and it is not free. I built a slight variation of this portfolio page in nine different ways and measured each one. I’ll present my research in a follow-up article.&lt;/p&gt;

&lt;p&gt;What do you think about these approaches? Have you thought of other ones? Did I miss a hidden trade-off? I’d be very happy to hear about it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference of community discussions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/TanStack/query/discussions/764" rel="noopener noreferrer"&gt;https://github.com/TanStack/query/discussions/764&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/TanStack/query/discussions/2178" rel="noopener noreferrer"&gt;https://github.com/TanStack/query/discussions/2178&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/TanStack/query/discussions/6337" rel="noopener noreferrer"&gt;https://github.com/TanStack/query/discussions/6337&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/TanStack/query/issues/7129" rel="noopener noreferrer"&gt;https://github.com/TanStack/query/issues/7129&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.nop33.com/blog/combining-tanstack-query-data/" rel="noopener noreferrer"&gt;nop33.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tanstackquery</category>
      <category>react</category>
    </item>
    <item>
      <title>Looking for (React) open-source contributors to reward</title>
      <dc:creator>nop33</dc:creator>
      <pubDate>Fri, 20 Dec 2024 12:11:00 +0000</pubDate>
      <link>https://dev.to/nop33/looking-for-react-open-source-contributors-to-reward-4mee</link>
      <guid>https://dev.to/nop33/looking-for-react-open-source-contributors-to-reward-4mee</guid>
      <description>&lt;p&gt;As our user base grows so does the GitHub issue backlog. My teammate and I at &lt;a href="//www.alephium.org"&gt;Alephium&lt;/a&gt; would be delighted to collaborate with open-source contributors from the React community. &lt;/p&gt;

&lt;p&gt;We will reward good code through our &lt;a href="https://github.com/alephium/community/blob/master/Grant%26RewardProgram.md" rel="noopener noreferrer"&gt;grands &amp;amp; rewards program&lt;/a&gt;. 🤑&lt;/p&gt;

&lt;p&gt;Our codebase consists of a monorepo with a React webapp, a React Electron app, and a React Native app, alongside shared packages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/alephium/alephium-frontend/" rel="noopener noreferrer"&gt;https://github.com/alephium/alephium-frontend/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hit us up on Discord, Telegram, GitHub, X, or wherever you can find us!&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Bun challenges: @shopify/react-native-skia</title>
      <dc:creator>nop33</dc:creator>
      <pubDate>Tue, 21 Nov 2023 13:51:00 +0000</pubDate>
      <link>https://dev.to/nop33/bun-challenges-shopifyreact-native-skia-1hl7</link>
      <guid>https://dev.to/nop33/bun-challenges-shopifyreact-native-skia-1hl7</guid>
      <description>&lt;p&gt;At work we recently switched from a poly-repo to a mono-repo set up and from using npm to using &lt;a href="https://bun.sh/" rel="noopener noreferrer"&gt;Bun&lt;/a&gt; as our project manager. One of the challenges that I faced today was that I could not run our React Native app in an Android simulator anymore. The main error I was getting was this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Cannot find source file:

      node_modules/@shopify/react-native-skia/android/cpp/jsi/JsiHostObject.cpp

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Click here for full traceback
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':shopify_react-native-skia:configureCMakeDebug[arm64-v8a]'.
&amp;gt; [CXX1429] error when building with cmake using /path/to/monorepo/node_modules/@shopify/react-native-skia/android/CMakeLists.txt: -- Android: Targeting API '21' with architecture 'arm64', ABI 'arm64-v8a', and processor 'aarch64'
  -- Android: Selected unified Clang toolchain
  -- The C compiler identification is Clang 12.0.8
  -- The CXX compiler identification is Clang 12.0.8
  -- Detecting C compiler ABI info
  -- Detecting C compiler ABI info - done
  -- Check for working C compiler: /path/to/android/sdk/ndk/23.1.7779620/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang - skipped
  -- Detecting C compile features
  -- Detecting C compile features - done
  -- Detecting CXX compiler ABI info
  -- Detecting CXX compiler ABI info - done
  -- Check for working CXX compiler: /path/to/android/sdk/ndk/23.1.7779620/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang++ - skipped
  -- Detecting CXX compile features
  -- Detecting CXX compile features - done
  -- Configuring done

  C++ build system [configure] failed while executing:
      /path/to/android/sdk/cmake/3.22.1/bin/cmake \
        -H/path/to/monorepo/node_modules/@shopify/react-native-skia/android \
        -DCMAKE_SYSTEM_NAME=Android \
        -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
        -DCMAKE_SYSTEM_VERSION=21 \
        -DANDROID_PLATFORM=android-21 \
        -DANDROID_ABI=arm64-v8a \
        -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a \
        -DANDROID_NDK=/path/to/android/sdk/ndk/23.1.7779620 \
        -DCMAKE_ANDROID_NDK=/path/to/android/sdk/ndk/23.1.7779620 \
        -DCMAKE_TOOLCHAIN_FILE=/path/to/android/sdk/ndk/23.1.7779620/build/cmake/android.toolchain.cmake \
        -DCMAKE_MAKE_PROGRAM=/path/to/android/sdk/cmake/3.22.1/bin/ninja \
        "-DCMAKE_CXX_FLAGS=-fexceptions -frtti -std=c++1y -DONANDROID" \
        -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=/path/to/monorepo/node_modules/@shopify/react-native-skia/android/build/intermediates/cxx/Debug/5v1r3a6q/obj/arm64-v8a \
        -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=/path/to/monorepo/node_modules/@shopify/react-native-skia/android/build/intermediates/cxx/Debug/5v1r3a6q/obj/arm64-v8a \
        -DCMAKE_BUILD_TYPE=Debug \
        -DCMAKE_FIND_ROOT_PATH=/path/to/monorepo/node_modules/@shopify/react-native-skia/android/.cxx/Debug/5v1r3a6q/prefab/arm64-v8a/prefab \
        -B/path/to/monorepo/node_modules/@shopify/react-native-skia/android/.cxx/Debug/5v1r3a6q/arm64-v8a \
        -GNinja \
        -DANDROID_STL=c++_shared \
        -DREACT_NATIVE_VERSION=72 \
        -DNODE_MODULES_DIR=/path/to/monorepo/node_modules \
        "-DPREBUILT_DIR=/path/to/monorepo/node_modules/@shopify/react-native-skia/android/build/react-native-0*/jni"
    from /path/to/monorepo/node_modules/@shopify/react-native-skia/android
  -- ABI : arm64-v8a
  -- PREBUILT: /path/to/monorepo/node_modules/@shopify/react-native-skia/android/build/react-native-0*/jni
  -- BUILD : /path/to/monorepo/node_modules/@shopify/react-native-skia/android/build
  -- LIBRN :
  -- LOG : /path/to/android/sdk/ndk/23.1.7779620/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/lib/aarch64-linux-android/21/liblog.so
  -- JSI : ReactAndroid::jsi
  -- REACT : ReactAndroid::react_nativemodule_core
  -- FBJNI : fbjni::fbjni
  -- TURBO : ReactAndroid::turbomodulejsijni
  CMake Error at CMakeLists.txt:40 (add_library):
    Cannot find source file:

      /path/to/monorepo/node_modules/@shopify/react-native-skia/android/cpp/jsi/JsiHostObject.cpp

    Tried extensions .c .C .c++ .cc .cpp .cxx .cu .mpp .m .M .mm .ixx .cppm .h
    .hh .h++ .hm .hpp .hxx .in .txx .f .F .for .f77 .f90 .f95 .f03 .hip .ispc

  CMake Error at CMakeLists.txt:40 (add_library):
    No SOURCES given to target: rnskia

  CMake Generate step failed. Build files cannot be regenerated correctly.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, the following file DID exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules/@shopify/react-native-skia/cpp/jsi/JsiHostObject.cpp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seems like some symlinking is not working properly.&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://github.com/Shopify/react-native-skia/issues/780" rel="noopener noreferrer"&gt;unrelated issue&lt;/a&gt; reminded me that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Bun does not execute arbitrary lifecycle scripts for installed dependencies, such as postinstall and node-gyp builds&lt;a href="https://bun.sh/guides/install/trusted" rel="noopener noreferrer"&gt;https://bun.sh/guides/install/trusted&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Adding a &lt;code&gt;trustedDependencies&lt;/code&gt; property in the package.json file of my project, deleting the &lt;code&gt;bun.lockb&lt;/code&gt; file and &lt;code&gt;node_modules&lt;/code&gt; and reinstalling with &lt;code&gt;bun i&lt;/code&gt; fixed the issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  "trustedDependencies": [
    "@shopify/react-native-skia"
  ]

rm -rf node_modules
rm bun.lockb
bun i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is also verified when looking at the logs og the &lt;code&gt;bun i&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Updating symlinks for Android build...
Creating symlink to api /path/to/monorepo/node_modules/@shopify/react-native-skia/scripts /path/to/monorepo/node_modules/@shopify/react-native-skia
Creating symlink to jsi /path/to/monorepo/node_modules/@shopify/react-native-skia/scripts /path/to/monorepo/node_modules/@shopify/react-native-skia
Creating symlink to rnskia /path/to/monorepo/node_modules/@shopify/react-native-skia/scripts /path/to/monorepo/node_modules/@shopify/react-native-skia
Creating symlink to skia /path/to/monorepo/node_modules/@shopify/react-native-skia/scripts /path/to/monorepo/node_modules/@shopify/react-native-skia
Creating symlink to utils /path/to/monorepo/node_modules/@shopify/react-native-skia/scripts /path/to/monorepo/node_modules/@shopify/react-native-skia
Symlinks created successfully.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Storing a function in React state</title>
      <dc:creator>nop33</dc:creator>
      <pubDate>Wed, 07 Jun 2023 14:11:00 +0000</pubDate>
      <link>https://dev.to/nop33/storing-a-function-in-react-state-1pl2</link>
      <guid>https://dev.to/nop33/storing-a-function-in-react-state-1pl2</guid>
      <description>&lt;p&gt;I felt very React-dumb today. I simply wanted to store a function in the state of a React component. So, I would initialize the component state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [myCallback, setMyCallback] = useState&amp;lt;() =&amp;gt; void&amp;gt;()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and later in the code I would have a button that sets this state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const cb = () =&amp;gt; console.log('hi!')
setMyCallback(cb)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’ve been breaking my head on why the hell my callback function gets called even though I never call it anywhere in my code! Notice that we didn’t execute &lt;code&gt;cb()&lt;/code&gt; or &lt;code&gt;myCallback()&lt;/code&gt; anywhere.&lt;/p&gt;

&lt;p&gt;The problem is that the argument of the &lt;code&gt;setState&lt;/code&gt; functions can be a function that let’s us update the state based on the previous value. See the &lt;a href="https://react.dev/reference/react/Component#setstate" rel="noopener noreferrer"&gt;docs of &lt;code&gt;setState&lt;/code&gt;&lt;/a&gt; for details. React cannot tell if the function we pass is the aforementioned function, or the value that we want to store in our state. A solution to this is to simply wrap the value we want to store into a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;setMyCallback(() =&amp;gt; cb)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So funny yet so frustrating at the same time! Hope I save someone’s time!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Using npm distribution tags the right way</title>
      <dc:creator>nop33</dc:creator>
      <pubDate>Sat, 02 Apr 2022 15:06:00 +0000</pubDate>
      <link>https://dev.to/nop33/using-npm-distribution-tags-the-right-way-562f</link>
      <guid>https://dev.to/nop33/using-npm-distribution-tags-the-right-way-562f</guid>
      <description>&lt;p&gt;I learnt a lot about npm package distribution tags at work in the past couple of weeks and I thought to share my learnings in this blog post.&lt;/p&gt;

&lt;p&gt;Publishing a package on npm is quite simple. All you need is an account on npmjs.com and a project containing a &lt;code&gt;package.json&lt;/code&gt; file. Then, with a few simple commands you can publish your package to npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm login
npm publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above command will pack your project and deploy it to the npm registry. What’s more, is that it will make the &lt;code&gt;latest&lt;/code&gt; distribution tag of your npm package point to the version of the package that is defined in the &lt;code&gt;package.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example package.json file&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.1.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;But what is a &lt;a href="https://docs.npmjs.com/cli/v8/commands/npm-dist-tag" rel="noopener noreferrer"&gt;distribution tag&lt;/a&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What this means is that when a user of your package installs it with &lt;code&gt;npm i my-awesome-package&lt;/code&gt; or &lt;code&gt;npm i my-awesome-package@latest&lt;/code&gt; it will install version &lt;code&gt;0.1.0&lt;/code&gt; from the example above. So, when running &lt;code&gt;npm publish&lt;/code&gt; in a project where the &lt;code&gt;version&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt; is set to &lt;code&gt;0.1.0&lt;/code&gt;, the &lt;code&gt;latest&lt;/code&gt; package distribution tag is an alias to the &lt;code&gt;0.1.0&lt;/code&gt; version of your package.&lt;/p&gt;

&lt;p&gt;But what if you want to publish a version of your package to npm, but you don’t want the &lt;code&gt;latest&lt;/code&gt; alias to point to that version? You may want, for example, to publish a release candidate version (say &lt;code&gt;1.0.0-rc.0&lt;/code&gt;) so that your users can download it (with &lt;code&gt;npm i my-awesome-package@1.0.0-rc.0&lt;/code&gt;) and test your upcoming changes before you publish a new patch/minor/major version of your package. But while you want your release candidate to be available on npmjs.com for people to find and download, you don’t want this version to show up when users are upgrading their packages, for example, with &lt;code&gt;npm outdated&lt;/code&gt; and &lt;code&gt;npm upgrade&lt;/code&gt;, nor when users install it with &lt;code&gt;npm i my-awesome-package&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The answer is in the &lt;a href="https://docs.npmjs.com/cli/v8/commands/npm-publish" rel="noopener noreferrer"&gt;docs of &lt;code&gt;npm publish&lt;/code&gt;&lt;/a&gt;. Running &lt;code&gt;npm publish&lt;/code&gt; is the same as running &lt;code&gt;npm publish --tag latest&lt;/code&gt;. That’s why the package version is always mapped to the &lt;code&gt;latest&lt;/code&gt; distribution tag. You can easily override this, by simply providing a different distribution tag. The mistake I made (guilty for not reading the &lt;a href="https://docs.npmjs.com/cli/v8/commands/npm-dist-tag" rel="noopener noreferrer"&gt;distribution tags docs&lt;/a&gt; properly) was that I thought “OK, I can just pass the version of my package as the tag and then the &lt;code&gt;latest&lt;/code&gt; won’t point to it”. So I tried:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm publish &lt;span class="nt"&gt;--tag&lt;/span&gt; 1.0.0-rc.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;only to receive the error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm ERR! Tag name must not be a valid SemVer range
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Going back to &lt;a href="https://docs.npmjs.com/cli/v8/commands/npm-dist-tag#caveats" rel="noopener noreferrer"&gt;the docs&lt;/a&gt; helped me understand the issue better. But it was this &lt;a href="https://stackoverflow.com/a/48038690/1644591" rel="noopener noreferrer"&gt;StackOverflow answer&lt;/a&gt; that really brought the issue home.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Package distribution tags are not meant to be version numbers. They are aliases to version numbers. They are words like &lt;code&gt;latest&lt;/code&gt;, &lt;code&gt;stable&lt;/code&gt;, &lt;code&gt;next&lt;/code&gt;, &lt;code&gt;rc&lt;/code&gt;, &lt;code&gt;experimental&lt;/code&gt;, etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, the correct way to publish a release candidate to npm, without making the &lt;code&gt;latest&lt;/code&gt; alias point to it is to run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm publish &lt;span class="nt"&gt;--tag&lt;/span&gt; rc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Workflow for versioning and publishing
&lt;/h2&gt;

&lt;p&gt;Overall, this is the workflow I follow to version my package and publish it on npm:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt; : Bump the version of the package with &lt;code&gt;npm version&lt;/code&gt; to create a git commit and a git tag (not to be confused with package distribution tags as I did above):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm version patch &lt;span class="c"&gt;# for a patch version&lt;/span&gt;
npm version minor &lt;span class="c"&gt;# for a minor version&lt;/span&gt;
npm version major &lt;span class="c"&gt;# for a major version&lt;/span&gt;
npm version prerelease &lt;span class="nt"&gt;--pre-id&lt;/span&gt; rc &lt;span class="c"&gt;# for a release candidate version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt; : Have a GitHub Action workflow that runs when pushing tags on GitHub. The workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;reads the name of the tag that was pushed&lt;/li&gt;
&lt;li&gt;defines the distribution tag (&lt;code&gt;latest&lt;/code&gt; if the tag matches the pattern &lt;code&gt;X.Y.Z&lt;/code&gt; or &lt;code&gt;rc&lt;/code&gt; if it matches the pattern &lt;code&gt;X.Y.Z-rcN&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;runs &lt;code&gt;npm publish --tag [latest|rc]&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt; : Push generated tag on GitHub to trigger the workflow&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt; : Profit ✅&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips
&lt;/h2&gt;

&lt;p&gt;Something that helped me clear things out in my head was the command &lt;code&gt;npm view&lt;/code&gt;. You can run it to inspect any published package on npm. For example, you can use it to inspect the distribution tags of a package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm view web3 dist-tags
&lt;span class="o"&gt;{&lt;/span&gt; latest: &lt;span class="s1"&gt;'1.7.1'&lt;/span&gt;, next: &lt;span class="s1"&gt;'2.0.0-alpha.1'&lt;/span&gt;, rc: &lt;span class="s1"&gt;'1.7.2-rc.0'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>npm</category>
      <category>dist</category>
      <category>publish</category>
    </item>
    <item>
      <title>Git commits from GitLab CI</title>
      <dc:creator>nop33</dc:creator>
      <pubDate>Wed, 04 Aug 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/nop33/git-commits-from-gitlab-ci-2l90</link>
      <guid>https://dev.to/nop33/git-commits-from-gitlab-ci-2l90</guid>
      <description>&lt;p&gt;Last week I implemented a search functionality for a static website with &lt;a href="https://lunrjs.com/" rel="noopener noreferrer"&gt;Lunr.js&lt;/a&gt;. The way I implemented the search system, depends on 2 files to work, the &lt;code&gt;pagesData.json&lt;/code&gt; and the &lt;code&gt;searchIndex.json&lt;/code&gt;. These 2 files get generated by running an npm script (I called it &lt;code&gt;yarn createSearchIndex&lt;/code&gt;) which will parse all markdown files of the static website (pages and blog posts) and create the 2 aforementioned files. Obviously, I do not want to be responsible for running this script on my machine every time someone publishes a new blog post through our CMS (we use &lt;a href="https://www.netlifycms.org/" rel="noopener noreferrer"&gt;NetlifyCMS&lt;/a&gt;, so when someone wants to create a new blog post, they simply log in into the CMS, create the blog post, and the CMS will create a new commit on the branch NetlifyCMS connected to). What I want is the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User commits a new blog post in branch &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A GitLab CI pipeline is triggered, which runs the &lt;code&gt;yarn createSearchIndex&lt;/code&gt; script, which in turn generates the new &lt;code&gt;pagesData.json&lt;/code&gt; and the &lt;code&gt;searchIndex.json&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;GitLab CI commits the new &lt;code&gt;pagesData.json&lt;/code&gt; and &lt;code&gt;searchIndex.json&lt;/code&gt; files without triggering a new CI pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Allowing GitLab runner to push to the git repo
&lt;/h2&gt;

&lt;p&gt;We need to ensure that the GitLab runner machine can push new changes to our repository, so we need to have an SSH key and a git user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create SSH key pair
&lt;/h3&gt;

&lt;p&gt;Create a public (&lt;code&gt;deploy_key.pub&lt;/code&gt;) and private (&lt;code&gt;deploy_key&lt;/code&gt;) SSH key pair without a passphrase with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh-keygen -f deploy_key -N ""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create environment variables
&lt;/h3&gt;

&lt;p&gt;In the &lt;code&gt;settings/ci_cd&lt;/code&gt; page of your GitLab project, define 3 environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CI_GIT_USER_EMAIL&lt;/code&gt; with the email of the git user that will appear in the commits made by the GitLab runner&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CI_GIT_USER_USERNAME&lt;/code&gt; with the name of the git user that will appear in the commits made by the GitLab runner&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SSH_PRIVATE_KEY&lt;/code&gt; with the contents of the private SSH key (you can copy the contents with &lt;code&gt;pbcopy &amp;lt; deploy_key&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.iliascreates.com%2Fstatic%2Fa8db2c0a6ef7848bd72db61acd1b810e%2Fe9b56%2Fci-vars.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.iliascreates.com%2Fstatic%2Fa8db2c0a6ef7848bd72db61acd1b810e%2Fe9b56%2Fci-vars.jpg" alt="CI environment variables" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create deploy key
&lt;/h3&gt;

&lt;p&gt;In the &lt;code&gt;settings/repository&lt;/code&gt; page of the GitLab project we need to create a &lt;strong&gt;deploy key&lt;/strong&gt;. Create a new deploy key and paste the contents of the previously generated public key (&lt;code&gt;pbcopy &amp;lt; deploy_key.pub&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Make sure to enable &lt;em&gt;Grant write permissions to this key&lt;/em&gt;, otherwise the runner won’t be able to push new commits.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.iliascreates.com%2Fstatic%2F830a2cdb1e175eb135bb6050d6fa84ed%2F82b8c%2Fci-deploy-key.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.iliascreates.com%2Fstatic%2F830a2cdb1e175eb135bb6050d6fa84ed%2F82b8c%2Fci-deploy-key.jpg" alt="CI deploy key" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Write .gitlab-ci.yml file
&lt;/h3&gt;

&lt;p&gt;A basic version of my &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:14&lt;/span&gt;

&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;commit&lt;/span&gt;

&lt;span class="na"&gt;update-index&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;commit&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Set up the SSH key and the known_hosts file&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;which&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ssh-agent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;apt-get&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;update&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-y&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;apt-get&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;install&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;openssh-client&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-y&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;)'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;eval $(ssh-agent -s)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "${SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add - &amp;gt; /dev/null&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mkdir -p ~/.ssh&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ssh-keyscan my.selfhostedgitlab.com &amp;gt;&amp;gt; ~/.ssh/known_hosts&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Configure git&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git config --global user.email "${CI_GIT_USER_EMAIL}"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git config --global user.name "${CI_GIT_USER_USERNAME}"&lt;/span&gt;
      &lt;span class="s"&gt;# Cone git repo&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git clone git@my.selfhostedgitlab.com:${CI_PROJECT_PATH}.git&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd ${CI_PROJECT_NAME}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git checkout main&lt;/span&gt;
      &lt;span class="s"&gt;# run scripts to generate new pagesData.json and searchIndex.json&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;yarn &amp;amp;&amp;amp; yarn createSearchIndex&lt;/span&gt;
      &lt;span class="s"&gt;# Check if there is something to commit, commit files, and push&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;if [[-n $(git status --porcelain)]]; then&lt;/span&gt;
        &lt;span class="s"&gt;git add src/pagesData.json src/searchindex.json &amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="s"&gt;git commit -m "Update search index" &amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="s"&gt;git push origin main -o ci.skip&lt;/span&gt;
      &lt;span class="s"&gt;fi&lt;/span&gt;
  &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to add the &lt;code&gt;-o ci.skip&lt;/code&gt; flag in the &lt;code&gt;git push&lt;/code&gt; command, so that you don’t trigger another pipeline.&lt;/p&gt;

&lt;p&gt;The commit should then appear, with the name of the git user you have configured:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.iliascreates.com%2Fstatic%2F219584c2b0e939bd9c6ddc759306f156%2F82b8c%2Fci-commit.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.iliascreates.com%2Fstatic%2F219584c2b0e939bd9c6ddc759306f156%2F82b8c%2Fci-commit.jpg" alt="CI commit" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;These resources helped me figure it out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://forum.gitlab.com/t/git-push-from-inside-a-gitlab-runner/30554" rel="noopener noreferrer"&gt;https://forum.gitlab.com/t/git-push-from-inside-a-gitlab-runner/30554&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://about.gitlab.com/blog/2017/11/02/automating-boring-git-operations-gitlab-ci/" rel="noopener noreferrer"&gt;https://about.gitlab.com/blog/2017/11/02/automating-boring-git-operations-gitlab-ci/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marcosschroh.github.io/posts/autobumping-with-gitlab/" rel="noopener noreferrer"&gt;https://marcosschroh.github.io/posts/autobumping-with-gitlab/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://forum.gitlab.com/t/is-it-possible-to-commit-artifacts-into-the-git/22218/7" rel="noopener noreferrer"&gt;https://forum.gitlab.com/t/is-it-possible-to-commit-artifacts-into-the-git/22218/7&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@pankajpatel?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Pankaj Patel&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/gitlab?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gitlab</category>
      <category>ci</category>
      <category>git</category>
      <category>commit</category>
    </item>
    <item>
      <title>Question regarding best practice on managing state</title>
      <dc:creator>nop33</dc:creator>
      <pubDate>Thu, 22 Jul 2021 12:52:04 +0000</pubDate>
      <link>https://dev.to/nop33/question-regarding-best-practice-on-managing-state-h89</link>
      <guid>https://dev.to/nop33/question-regarding-best-practice-on-managing-state-h89</guid>
      <description>&lt;p&gt;I am working on an app that presents a list of questions to the user and expects answers. The representation of data is something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Career&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Career&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Love&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Love&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My question is, should I store this whole object in the state, or only store the answers?&lt;/p&gt;

&lt;p&gt;In the first case, the code would look something like the following. Here we can see that questions and answers are bundled together, which makes it easier to iterate over them and display them together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WheelOfLife&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;stepsData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setStepsData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Career&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Career&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Love&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Love&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setAnswer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newStepsData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;stepsAnswers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;newStepsData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt;
    &lt;span class="nf"&gt;setStepsAnswers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newStepsData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stepsData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the second case, I am following the recommendation of the &lt;a href="https://reactjs.org/docs/thinking-in-react.html" rel="noopener noreferrer"&gt;React docs&lt;/a&gt; that says &lt;em&gt;"Does it remain unchanged over time? If so, it probably isn’t state."&lt;/em&gt;, but now questions and answers are separated and it becomes a tiny bit more complicated to access the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stepsData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Career&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Career&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Love&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Love&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WheelOfLife&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;stepsAnswers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setStepsAnswers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stepsData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setAnswer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newStepsAnswers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;stepsAnswers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;newStepsAnswers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt;
    &lt;span class="nf"&gt;setStepsAnswers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newStepsAnswers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stepsData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stepsAnswers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thank you for the feedback!&lt;/p&gt;

</description>
      <category>react</category>
      <category>question</category>
      <category>state</category>
    </item>
    <item>
      <title>Splitting huge NetlifyCMS config.yml file to multiple JS files</title>
      <dc:creator>nop33</dc:creator>
      <pubDate>Thu, 15 Jul 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/nop33/splitting-huge-netlifycms-config-yml-file-to-multiple-js-files-4hn0</link>
      <guid>https://dev.to/nop33/splitting-huge-netlifycms-config-yml-file-to-multiple-js-files-4hn0</guid>
      <description>&lt;p&gt;For a long time, I have been procrastinating in finding a solution to my ever-growing NetlifyCMS config.yml file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;The website I have been working on consists of 18 unique pages (home page, about page, contact page, etc) as well as of a blog feature. The content of each page is stored in frontmatter inside markdown files, except for the content of the blog posts which is stored in actual markdown.&lt;/p&gt;

&lt;p&gt;To demonstrate the differences between the content of each page, have a look at the following snippets of 3 of those pages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Home page&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Home&lt;/span&gt;
&lt;span class="na"&gt;seo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lorem ipsum dolor sit amet&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
    &lt;span class="s"&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do&lt;/span&gt;
    &lt;span class="s"&gt;eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim&lt;/span&gt;
    &lt;span class="s"&gt;veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea&lt;/span&gt;
    &lt;span class="s"&gt;commodo consequat.&lt;/span&gt;
&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;text_part_1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sed ut perspiciatis&lt;/span&gt;
    &lt;span class="na"&gt;text_part_2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unde omnis iste natus&lt;/span&gt;
  &lt;span class="na"&gt;button&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lorem ipsum&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/lorem&lt;/span&gt;
  &lt;span class="na"&gt;subtitle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&lt;/span&gt;
&lt;span class="na"&gt;key_facts_section&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;At vero eos et accusamus&lt;/span&gt;
  &lt;span class="na"&gt;key_facts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Et harum quidem&lt;/span&gt;
      &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;static/images/icons/clock.svg&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Nam libero tempore&lt;/span&gt;
      &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;static/images/icons/earth.svg&lt;/span&gt;
&lt;span class="na"&gt;testimonials&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;...&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;About page&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;About&lt;/span&gt;
&lt;span class="na"&gt;menu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;footer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40&lt;/span&gt;
  &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
&lt;span class="na"&gt;seo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Quis autem vel eum&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Et harum quidem rerum facilis est et expedita distinctio.&lt;/span&gt;
&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;About&lt;/span&gt;
&lt;span class="na"&gt;video_section&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
    &lt;span class="s"&gt;Et harum quidem rerum facilis est et expedita distinctio. Nam libero&lt;/span&gt;
    &lt;span class="s"&gt;tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus&lt;/span&gt;
    &lt;span class="s"&gt;id quod maxime placeat facere possimus.&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Rerum necessitatibus saepe&lt;/span&gt;
  &lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://player.vimeo.com/video/845028457&lt;/span&gt;
&lt;span class="na"&gt;team_section&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;...&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Blog post&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Et harum quidem rerum facilis est et expedita&lt;/span&gt;
&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2017-02-28&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
  &lt;span class="s"&gt;Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit&lt;/span&gt;
  &lt;span class="s"&gt;quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda&lt;/span&gt;
  &lt;span class="s"&gt;est, omnis dolor repellendus. Temporibus autem quibusdam et aut.&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/images/uploads/featured-image.jpg&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;This is the body of the **blog post** , written in markdown.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see the content of each page is vastly different from the others, yet there are some patterns that repeat themselves. To create a NetlifyCMS configuration file for all of the 18 pages and other data files I needed a &lt;strong&gt;2’000 lines of code&lt;/strong&gt; config.yml file. This approach works, but the code is not very DRY and it is really hard to change something or add something new. It’s time to improve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the NetlifyCMS configuration code DRY
&lt;/h2&gt;

&lt;p&gt;One valid approach to not repeatting parts of the YAML configuration would be to use &lt;a href="https://support.atlassian.com/bitbucket-cloud/docs/yaml-anchors/" rel="noopener noreferrer"&gt;YAML anchors (&lt;code&gt;&amp;amp;&lt;/code&gt;), aliases (&lt;code&gt;*&lt;/code&gt;), and overrides (&lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt;)&lt;/a&gt;. But I discovered a better approach. ✨&lt;/p&gt;

&lt;h3&gt;
  
  
  Using JavaScript instead of YAML
&lt;/h3&gt;

&lt;p&gt;Through the &lt;a href="https://github.com/netlify/netlify-cms/issues/3624#issuecomment-616076374" rel="noopener noreferrer"&gt;related GitHub issue&lt;/a&gt; and the new &lt;a href="https://www.netlifycms.org/docs/beta-features/#manual-initialization" rel="noopener noreferrer"&gt;beta NetlifyCMS feature of manual initialization&lt;/a&gt; I discovered that I am not restricted to using YAML to configure the CMS. Using JS does not only allow me to split the configuration into multiple files but to create objects and functions to keep my code DRY as well.&lt;/p&gt;

&lt;h4&gt;
  
  
  File structure
&lt;/h4&gt;

&lt;p&gt;The file structure that fits my needs is the following (the tree view is simplified to only show the related pages from the example snippets I mentioned above). Below I explain what the purpose and contents of each file are.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;└── cms
    ├── index.js
    ├── editor-components.js
    └── config/
        ├── index.js
        ├── fields.js
        ├── patterns.js
        └── collections/
            ├── blog-posts
            │ └── index.js
            └── pages
                ├── about.js
                ├── blog.js
                ├── home.js
                └── index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  &lt;code&gt;cms/index.js&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;Here is where I import the NetlifyCMS library. This file will eventually be parsed by Webpack in my setup and included in the &lt;code&gt;admin.html&lt;/code&gt; file that is used to load the CMS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Import NetlifyCMS library&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CMS&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;netlify-cms&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;netlify-cms/dist/cms.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Import custom editor component from cms/editor-components.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;myCustomEditorComponent&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./editor-components&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Import NetlifyCMS JS configuration object from cms/config/index.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Disable loading of the configuration from the default config.yml file&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CMS_MANUAL_INIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Initialize NetlifyCMS with the JS configuration objext&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CMS_CONFIGURATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;CMS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Register the custom editor component&lt;/span&gt;
&lt;span class="nx"&gt;CMS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerEditorComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myCustomEditorComponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  &lt;code&gt;cms/editor-components.js&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;This file is not necessary to explain the setup, I’ve written another blog post on &lt;a href="https://dev.to/blog/post/embeding-youtube-videos-markdown-gatsby-netlifycms/"&gt;creating custom NetlifyCMS editor components&lt;/a&gt;, check it out!&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;code&gt;cms/config/index.js&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;This is where we build our NetlifyCMS JS configuration object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Import the configuration of each collection from cms/config/collections&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;blogPostsCollection&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./collections/blog-posts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;pagesCollection&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./collections/pages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;pressReleasesCollection&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./collections/press-releases&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;servicesCollection&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./collections/services&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;siteConfigurationCollection&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./collections/site-configuration&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;testimonialsCollection&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./collections/testimonials&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Build the Netlify JS configuration object&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gitlab&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;website&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;staging&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;auth_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;implicit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;app_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MY_APP_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;api_root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://my-self-hosted-gitlab.com/api/v4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;base_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://my-self-hosted-gitlab.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;auth_endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;oauth/authorize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// It is not required to set `load_config_file` if the `config.yml` file is&lt;/span&gt;
  &lt;span class="c1"&gt;// missing, but will improve performance and avoid a load error.&lt;/span&gt;
  &lt;span class="na"&gt;load_config_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;publish_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editorial_workflow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;media_folder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;site/static/images/uploads&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;public_folder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/images/uploads&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// Include the collections imported from cms/config/collections&lt;/span&gt;
    &lt;span class="nx"&gt;pagesCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;servicesCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;blogPostsCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;commonPageSectionsCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;testimonialsCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;pressReleasesCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;siteConfigurationCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  &lt;code&gt;cms/config/collections/pagesCollection/index.js&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;This file groups together all the configuration objects from each individual page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;collectionDefaults&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../patterns&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;homePageConfig&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./home&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;aboutPageConfig&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./about&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pagesCollection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;collectionDefaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;homePageConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aboutPageConfig&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;pagesCollection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  &lt;code&gt;cms/config/collections/pagesCollection/home.js&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;The configuration of a page. It utilizes fields and groups of fields (patterns) to keep the configuration code of the page DRY.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;objectField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listField&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../fields&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;pageDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;buttonDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;titleWithSubtitleDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../patterns&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;site/content/_index.md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pageDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;objectField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;titleWithSubtitleDefaults&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nf"&gt;buttonDefaults&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="nf"&gt;objectField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Keyfacts section&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keyfacts_section&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;titleWithSubtitleDefaults&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nf"&gt;listField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Keyfacts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keyfacts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Icon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;icon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  &lt;code&gt;cms/config/fields.js&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;The basic building blocks of the configuration file. Creating functions that return JS objects allows us to keep the code DRY by passing the necessary parameters when needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stringField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;String&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;objectField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="s2"&gt;`
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  &lt;code&gt;cms/config/patterns.js&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;Sometimes, patterns in the configuration of pages and collections emerge. This file groups fields together and exports these patterns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;objectField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hiddenField&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./fields&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collectionDefaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageDefaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nf"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Menu title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;hiddenField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Menu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;objectField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SEO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;seo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SEO title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SEO description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;]),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;multiColorTitleDefaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;objectField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nf"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text part 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text_part_1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text part 2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text_part_2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buttonDefaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;objectField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;titleWithSubtitleDefaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subtitleIsMarkdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;multiColorTitleDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;subtitleIsMarkdown&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;markdownField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Subtitle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;subtitle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Subtitle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;subtitle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The above setup helped me reduce the total lines of code from around 2000 to just 900. It has also been proven that making updates to the configuration of each page has become a piece of cake 🍰 (navigating to the dedicated file is very easy by typing for example &lt;em&gt;“cms about”&lt;/em&gt; into the search bar of my editor to reach the configuration of the about page).&lt;/p&gt;

&lt;h2&gt;
  
  
  Other resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mrkaluzny.com/blog/dry-netlify-cms-config-with-manual-initialization/" rel="noopener noreferrer"&gt;Configuring NetlicyCMS manual initialization in Gatsby&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Embedding YouTube videos in Gatsby blog posts using NetlifyCMS</title>
      <dc:creator>nop33</dc:creator>
      <pubDate>Thu, 03 Jun 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/nop33/embedding-youtube-videos-in-gatsby-blog-posts-using-netlifycms-263j</link>
      <guid>https://dev.to/nop33/embedding-youtube-videos-in-gatsby-blog-posts-using-netlifycms-263j</guid>
      <description>&lt;p&gt;In one of the websites I have built, I needed to be able to embed YouTube videos in the markdown content of the blog posts. I am using &lt;a href="https://www.gatsbyjs.com/" rel="noopener noreferrer"&gt;Gatsby&lt;/a&gt; together with &lt;a href="https://www.netlifycms.org/" rel="noopener noreferrer"&gt;NetlifyCMS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Two are the things that I had to figure out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How do I represent the YouTube video in markdown so that the &lt;code&gt;gatsby-transformer-remark&lt;/code&gt; plugin (which is responsible for converting markdown to HTML) can render it? Bonus: How do I make sure that the iframe of the embedded videos is responsive and looks good on mobile devices?&lt;/li&gt;
&lt;li&gt;How do I extend NetlifyCMS’ markdown widget to support adding YouTube links in the content, without forcing the user to switch to the markdown view?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Gatbsy part
&lt;/h2&gt;

&lt;p&gt;Thankfully, Gatsby’s ecosystem is huge! From Gatsby’s &lt;a href="https://www.gatsbyjs.com/docs/how-to/images-and-media/working-with-video/" rel="noopener noreferrer"&gt;Working with Video&lt;/a&gt; guide, I discovered the &lt;a href="https://www.gatsbyjs.com/plugins/gatsby-remark-embed-video/" rel="noopener noreferrer"&gt;&lt;code&gt;gatsby-remark-embed-video&lt;/code&gt;&lt;/a&gt; plugin. This allows me to represent a YouTube video in markdown as a simple code block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`youtube: https://www.youtube.com/watch?v=dQw4w9WgXcQ`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simply install the plugin and include it in the list of plugins in &lt;code&gt;gatsby-config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add gatsby-remark-embed-video
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To ensure that the video will be responsive, use the &lt;code&gt;gatsby-remark-responsive-iframe&lt;/code&gt; plugin and place it &lt;strong&gt;after&lt;/strong&gt; the &lt;code&gt;gatsby-remark-embed-video&lt;/code&gt; one, as &lt;a href="https://www.gatsbyjs.com/plugins/gatsby-remark-embed-video/?=video#install" rel="noopener noreferrer"&gt;recommended&lt;/a&gt; by the plugin’s author:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`gatsby-transformer-remark`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;`gatsby-remark-embed-video`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;`gatsby-remark-images`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;`gatsby-remark-responsive-iframe`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now add a YouTube video in your markdown file and Gatsby will know how to render it. Your markdown file should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Here&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;blog&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;post&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;YouTube&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;video"&lt;/span&gt;
&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2020-06-03"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gu"&gt;## This is my fav song of all times&lt;/span&gt;

Don't cha think?

&lt;span class="sb"&gt;`youtube: https://www.youtube.com/watch?v=dQw4w9WgXcQ`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The NetlifyCMS part
&lt;/h2&gt;

&lt;p&gt;Great, now all we need is to figure out how to extend NetlifyCMS’ markdown widget to be able to add code blocks like the above to our markdown content. Checking out the &lt;a href="https://www.netlifycms.org/docs/widgets/#markdown" rel="noopener noreferrer"&gt;official documentation of the markdown widget&lt;/a&gt; I discovered the guide about &lt;a href="https://www.netlifycms.org/docs/custom-widgets/#registereditorcomponent" rel="noopener noreferrer"&gt;Creating Custom Widgets&lt;/a&gt; and specifically the &lt;code&gt;registerEditorComponent&lt;/code&gt; method. And hey, what luck, there is an actual example of registering an editor component for adding YouTube videos! Today is a good day.&lt;/p&gt;

&lt;p&gt;The question now is, where do I add the code to call this method? I am simply using the &lt;code&gt;gatsby-plugin-netlify-cms&lt;/code&gt; plugin in my &lt;code&gt;gatsby-config.js&lt;/code&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`gatsby-plugin-netlify-cms`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Diving into the &lt;a href="https://www.gatsbyjs.com/plugins/gatsby-plugin-netlify-cms/#options" rel="noopener noreferrer"&gt;documentation of the plugin&lt;/a&gt;, I discovered the &lt;code&gt;modulePath&lt;/code&gt; option which lets me specify a path to a file where I can import the &lt;code&gt;CMS&lt;/code&gt; object. Sweet!&lt;/p&gt;

&lt;p&gt;Update your configuration to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`gatsby-plugin-netlify-cms`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/src/cms.js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and create the &lt;code&gt;cms.js&lt;/code&gt; file in the &lt;code&gt;/src&lt;/code&gt; directory (feel free to place it wherever you want as long as you link it properly in the &lt;code&gt;gatsby-config.js&lt;/code&gt; file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CMS&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;netlify-cms-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="nx"&gt;CMS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerEditorComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;youtube&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YouTube&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Youtube video URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/^`youtube:&lt;/span&gt;&lt;span class="se"&gt;\s(&lt;/span&gt;&lt;span class="sr"&gt;.*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;`$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fromBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;toBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;`youtube: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;toPreview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To explain what's going on above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;pattern&lt;/code&gt; attribute defines the regular expression that will look for the line in the markdown content that starts with the string "`youtube: " and catch anything that comes after it, which in our case is the link to the video.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;fromBlock&lt;/code&gt; attribute defines a function that is responsible to initialize the value of the rendered widget. In other words, it takes the link of the video from the &lt;code&gt;pattern&lt;/code&gt; and adds it in the input field of the YouTube widget.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;toBlock&lt;/code&gt; function defines the representation of the YouTube video in the markdown content. To comply with the &lt;code&gt;gatsby-remark-embed-video&lt;/code&gt; Gatsby plugin, this needs to be a line that starts with a backtick, followed by the word "youtube: ", followed by the video link, and ends with another backtick.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it! You can now go to your CMS page, click the ”+” icon in the markdown editor, select “YouTube”, paste your YouTube link and save your blog post.&lt;/p&gt;

&lt;p&gt;&lt;a href="/static/55f3730698e71321addb6711b12b4a20/b89ad/markdown-widget-youtube.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Filiascreates.com%2Fstatic%2F55f3730698e71321addb6711b12b4a20%2F828fb%2Fmarkdown-widget-youtube.jpg" title="NetlifyCMS markdown widget" alt="NetlifyCMS markdown widget" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  But what about Vimeo and VideoPress?
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;gatsby-remark-embed-video&lt;/code&gt; plugin supports &lt;a href="https://www.gatsbyjs.com/plugins/gatsby-remark-embed-video/#usage" rel="noopener noreferrer"&gt;multiple video hosting services&lt;/a&gt; like Vimeo and VideoPress, not just YouTube (it also says it supports Twitch and Twitch live, but I couldn’t manage to make it work). Simply replace the instances of &lt;code&gt;youtube&lt;/code&gt; with &lt;code&gt;video&lt;/code&gt; in the &lt;code&gt;cms.js&lt;/code&gt; file to support them!&lt;/p&gt;

</description>
      <category>netlifycms</category>
      <category>gatsby</category>
      <category>video</category>
      <category>markdown</category>
    </item>
    <item>
      <title>From googling "React Native" to having a working app on my phone in 5 hours</title>
      <dc:creator>nop33</dc:creator>
      <pubDate>Thu, 20 May 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/nop33/from-googling-react-native-to-having-a-working-app-on-my-phone-in-5-hours-3jeg</link>
      <guid>https://dev.to/nop33/from-googling-react-native-to-having-a-working-app-on-my-phone-in-5-hours-3jeg</guid>
      <description>&lt;p&gt;This morning I challenged myself to see how difficult it would be to have a running Android app installed on my phone, starting from scratch. I wanted a simple app that given a number it would calculate a random integer number between 1 and the number I provided. I wanna use it for performing a “random walk” using the train system of Switzerland to explore the country. I know, silly, but why not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the dev environment
&lt;/h2&gt;

&lt;p&gt;I started by googling “React Native”. Headed to the &lt;a href="https://reactnative.dev/" rel="noopener noreferrer"&gt;official website&lt;/a&gt;. I read through the home page, run the command to start a new project, and went on to &lt;a href="https://reactnative.dev/docs/tutorial" rel="noopener noreferrer"&gt;read the basics&lt;/a&gt; and the &lt;a href="https://reactnative.dev/docs/getting-started" rel="noopener noreferrer"&gt;getting started guide&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npx react-native init MyTestApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing I saw in the &lt;a href="https://reactnative.dev/docs/getting-started" rel="noopener noreferrer"&gt;getting started guide&lt;/a&gt; is that I need to set up my dev environment. Since I am not familiar with mobile development, I decided to start with Expo CLI instead of the React Native CLI (which is what I used above to set up my first project). This &lt;a href="https://stackoverflow.com/questions/39170622/what-is-the-difference-between-expo-and-react-native" rel="noopener noreferrer"&gt;SO thread&lt;/a&gt; describes well the differences between the 2 cli’s.&lt;/p&gt;

&lt;p&gt;Simply following the &lt;a href="https://reactnative.dev/docs/environment-setup" rel="noopener noreferrer"&gt;Expo CLI Quickstart guide&lt;/a&gt; didn’t work. I installed the global expo-cli package, initialized a new project, started the development server, installed the &lt;a href="https://play.google.com/store/apps/details?id=host.exp.exponent&amp;amp;hl=en&amp;amp;gl=US" rel="noopener noreferrer"&gt;Expo Go Android app&lt;/a&gt; on my phone, connected both my laptop and my phone on the same WiFi, but there was no QR code on my terminal to scan with the Expo Go app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ yarn global add expo-cli
...
success Installed "expo-cli@4.4.7" with binaries:
      - expo
      - expo-cli
✨ Done in 65.40s.

$ expo init AwesomeProject
✔ Choose a template: › minimal bare and minimal, just the essentials to get you started
✔ Downloaded and extracted project files.
🧶 Using Yarn to install packages. Pass --npm to use npm instead.
✔ Installed JavaScript dependencies.

$ cd AwesomeProject
$ yarn start
yarn run v1.22.10
$ react-native start

               ######                ######
             ###     ####        ####     ###
            ##          ###    ###          ##
            ##             ####             ##
            ##             ####             ##
            ##           ##    ##           ##
            ##         ###      ###         ##
             ##  ########################  ##
          ######    ###            ###    ######
      ###     ##    ##              ##    ##     ###
   ###         ## ###      ####      ### ##         ###
  ##           ####      ########      ####           ##
 ##             ###     ##########     ###             ##
  ##           ####      ########      ####           ##
   ###         ## ###      ####      ### ##         ###
      ###     ##    ##              ##    ##     ###
          ######    ###            ###    ######
             ##  ########################  ##
            ##         ###      ###         ##
            ##           ##    ##           ##
            ##             ####             ##
            ##             ####             ##
            ##          ###    ###          ##
             ###     ####        ####     ###
               ######                ######

                 Welcome to React Native!
                Learn once, write anywhere

To reload the app press "r"
To open developer menu press "d"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next idea was to learn more about how this expo thing works. Going on &lt;a href="https://expo.io/" rel="noopener noreferrer"&gt;Expo’s website&lt;/a&gt;, clicking &lt;a href="https://docs.expo.io/" rel="noopener noreferrer"&gt;Get Started&lt;/a&gt;, moving to the &lt;a href="https://docs.expo.io/get-started/installation/" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt; and finally to the &lt;a href="https://docs.expo.io/get-started/create-a-new-app/" rel="noopener noreferrer"&gt;Create a new app guide&lt;/a&gt; I found the command that displayed the QR code on my screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ expo start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scanning the QR code with the &lt;a href="https://play.google.com/store/apps/details?id=host.exp.exponent&amp;amp;hl=en&amp;amp;gl=US" rel="noopener noreferrer"&gt;Expo Go app&lt;/a&gt; on my Android phone opened up the app on my phone. Editing the source code on my laptop changes quickly refreshes the app on my phone. Nice! The whole process took a bit less than 1.5h 🤦‍♂️ But whatever, we’re moving!&lt;/p&gt;

&lt;h2&gt;
  
  
  Coding the app
&lt;/h2&gt;

&lt;p&gt;Without having much experience with React, I managed to code the simple concept of an app that I mentioned earlier by simply reading the &lt;a href="https://reactnative.dev/docs/getting-started" rel="noopener noreferrer"&gt;Introduction&lt;/a&gt;, &lt;a href="https://reactnative.dev/docs/intro-react-native-components" rel="noopener noreferrer"&gt;Core Components and Native Components&lt;/a&gt;, &lt;a href="https://reactnative.dev/docs/intro-react" rel="noopener noreferrer"&gt;React Fundamentals&lt;/a&gt; and &lt;a href="https://reactnative.dev/docs/handling-text-input" rel="noopener noreferrer"&gt;Handling Text Input&lt;/a&gt; React Native guides. Great, the app seems to be working on the dev environment, let’s compile it and install it on my phone, I said.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compiling the app
&lt;/h2&gt;

&lt;p&gt;Initially, I was planning on releasing the app on the Google Play Store and thus started by following the &lt;a href="https://reactnative.dev/docs/signed-apk-android" rel="noopener noreferrer"&gt;Publishing to Google Play Store guide&lt;/a&gt; of the React Native documentation. I installed the latest Java JDK, generated a private signing key, set up the Grandle variables, added the signing configuration to my app’s Grandle config file only to realize that the Google Play store required a $25 fee for registering. I decided to postpone it for the day that I’ll actually have a useful app I’d like to share with the world 😅&lt;/p&gt;

&lt;p&gt;I moved on with generating the Android App Bundle with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd android
$ ./gradlew bundleRelease
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but I received the error&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;General error during semantic analysis: Unsupported class file major version 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason is that the Grandle 6.8 version that my app is using does not support the latest Java JDK (16). Someone &lt;a href="https://github.com/gradle/gradle/issues/13629#issuecomment-844561525" rel="noopener noreferrer"&gt;commented literally 11 hours ago&lt;/a&gt; that Gradle 7.0 fully supports Java 16, so I upgraded Grandle by simply changing the version in the file &lt;code&gt;android/gradle/wrapper/gradle-wrapper.properties&lt;/code&gt;, but now I got &lt;a href="https://github.com/expo/expo/issues/12774" rel="noopener noreferrer"&gt;a new error&lt;/a&gt; after running the above command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Plugin with id ‘maven’ not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decided to downgrade to Java 11:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ brew install java11
$ echo 'export PATH="/usr/local/opt/openjdk@11/bin:$PATH"' &amp;gt;&amp;gt; ~/.zshrc
$ export CPPFLAGS="-I/usr/local/opt/openjdk@11/include"
$ source ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but then I got yet another error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Could not determine the dependencies of task ':app:bundleReleaseResources'.
&amp;gt; SDK location not found. Define location with an ANDROID_SDK_ROOT environment variable or by setting the sdk.dir path in your project's local properties file at '.../android/local.properties'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After some googling on how to install the Android SDK (since &lt;a href="https://formulae.brew.sh/cask/android-sdk" rel="noopener noreferrer"&gt;the one I found in Homebrew&lt;/a&gt; is deprecated), I decided to install the &lt;a href="https://formulae.brew.sh/cask/android-studio" rel="noopener noreferrer"&gt;Android Studio&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ brew install --cask android-studio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I opened the application to download and install all necessary packages for Android development, including the Android SDK.&lt;/p&gt;

&lt;p&gt;But we’re not done with errors apparently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; Failed to install the following Android SDK packages as some licences have not been accepted.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To fix this, I &lt;a href="https://stackoverflow.com/questions/54273412/failed-to-install-the-following-android-sdk-packages-as-some-licences-have-not/66392874" rel="noopener noreferrer"&gt;installed the Android SDK command line tool&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Opened Android Studio&lt;/li&gt;
&lt;li&gt;Went to SDK manager&lt;/li&gt;
&lt;li&gt;Clicked on SDK tools tab&lt;/li&gt;
&lt;li&gt;Installed Android SDK command line tool&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For one last time, I run the build command and it finally worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./gradlew bundleRelease
...
BUILD SUCCESSFUL in 3m 14s
494 actionable tasks: 494 executed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing the app on my Android phone
&lt;/h2&gt;

&lt;p&gt;The last step of my challenge was to install the app on my phone. I figured the easiest way would be to move the output APK to my phone via USB. And so I did by installing and using the &lt;a href="https://formulae.brew.sh/cask/android-file-transfer" rel="noopener noreferrer"&gt;Android File Transfer&lt;/a&gt; app.&lt;/p&gt;

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

&lt;p&gt;A few years back I was extremely hesitant and scared of even attempting to create a native app of my own. My impostor syndrome was hitting me hard every time I was facing a problem. An inner voice was judging me that I am not good enough and that I should not even try. If you are stuck in a similar situation, I hope that this blog post will unblock you from a potential roadblock you’re facing. Keep believing in your skill to learn. Not in your dev skills. It might have taken me 5 hours to simply set up a dev environment, code a simple app and figure out how to compile it and install it, but I did it. And so can you.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Using Ansible unarchive module with Alpine Linux</title>
      <dc:creator>nop33</dc:creator>
      <pubDate>Mon, 05 Apr 2021 15:18:00 +0000</pubDate>
      <link>https://dev.to/nop33/using-ansible-unarchive-module-with-alpine-linux-3k3k</link>
      <guid>https://dev.to/nop33/using-ansible-unarchive-module-with-alpine-linux-3k3k</guid>
      <description>&lt;p&gt;If you are running &lt;a href="https://www.ansible.com/" rel="noopener noreferrer"&gt;Ansible&lt;/a&gt; on &lt;a href="https://alpinelinux.org/" rel="noopener noreferrer"&gt;Alpine Linux&lt;/a&gt; and you use the &lt;a href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/unarchive_module.html" rel="noopener noreferrer"&gt;&lt;code&gt;unarchive&lt;/code&gt; builtin module&lt;/a&gt; you might have faced the following error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Errno 2] No such file or directory: b'-T'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason for this is that the &lt;code&gt;unzip&lt;/code&gt; tool in Alpine does not have a &lt;code&gt;-T&lt;/code&gt; option. To be able to use the &lt;code&gt;unarchive&lt;/code&gt; Ansible module with Alpine Linux, you need to install &lt;code&gt;unzip&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apk add unzip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the &lt;a href="https://github.com/ansible/ansible/issues/39029" rel="noopener noreferrer"&gt;related GitHub issue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@simonfitall?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Simon Fitall&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/apls?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Recording screen, mic input and audio output with one app</title>
      <dc:creator>nop33</dc:creator>
      <pubDate>Fri, 26 Mar 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/nop33/recording-screen-mic-input-and-audio-output-with-one-app-58g4</link>
      <guid>https://dev.to/nop33/recording-screen-mic-input-and-audio-output-with-one-app-58g4</guid>
      <description>&lt;p&gt;I’ve been for years a very happy user of &lt;a href="https://getkap.co/" rel="noopener noreferrer"&gt;Kap&lt;/a&gt;, a free open-source app to record the screen of my MacBook.&lt;/p&gt;

&lt;p&gt;Recently I wanted to perform some interviews during a requirements gathering process for a new project at work. I needed a way to record my screen, my voice but also the voice of the interviewee. Essentially I needed to capture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Screen&lt;/li&gt;
&lt;li&gt;Audio input&lt;/li&gt;
&lt;li&gt;Audio output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, as usual, I started with a simple &lt;a href="https://www.google.com/search?q=record+screen+audio+input+and+output+mac&amp;amp;sxsrf=ALeKk03pGfOhM0PDc64SpqsGcRIu_a3Tkw%3A1616781405527&amp;amp;ei=XSBeYLnAH4X0kwXXk4TYDw&amp;amp;oq=record+screen+audio+input+and+output+mac&amp;amp;gs_lcp=Cgdnd3Mtd2l6EAM6BwgAEEcQsANQurYQWLq2EGDGuhBoA3ACeACAAVuIAZ4BkgEBMpgBAKABAaoBB2d3cy13aXrIAQjAAQE&amp;amp;sclient=gws-wiz&amp;amp;ved=0ahUKEwj54uuKxM7vAhUF-qQKHdcJAfsQ4dUDCA0&amp;amp;uact=5" rel="noopener noreferrer"&gt;Google search&lt;/a&gt; to see what is out there. I found many solutions that required downloading some apps (like &lt;a href="https://github.com/mattingalls/Soundflower" rel="noopener noreferrer"&gt;Soundflower&lt;/a&gt;, &lt;a href="https://rogueamoeba.com/loopback/" rel="noopener noreferrer"&gt;LoopBack&lt;/a&gt; or &lt;a href="https://www.telestream.net/screenflow/" rel="noopener noreferrer"&gt;ScreenFlow&lt;/a&gt;) and use some of them in combination with Quicktime Player, a native macOS app.&lt;/p&gt;

&lt;p&gt;5 minutes had passed and I already felt overwhelmed. I thought this was gonna be easier. And then it hit me: &lt;em&gt;“Why haven’t you checked if Kap can do that?”&lt;/em&gt;. Dah!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All I literally had to do was to go to the preferences of the app and turn 2 toggles on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Enable &lt;strong&gt;Audio recording&lt;/strong&gt; from the General tab&lt;/li&gt;
&lt;li&gt;Enable the &lt;strong&gt;soundflower plugin&lt;/strong&gt; from the Plugins tab&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finito.&lt;/p&gt;

&lt;p&gt;&lt;a href="/static/2d412def287cfcdf24aec2dedaad4a6b/7321b/general-preferences.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Filiascreates.com%2Fstatic%2F2d412def287cfcdf24aec2dedaad4a6b%2Ff058b%2Fgeneral-preferences.png" title="Kap general preferences tab" alt="Kap general preferences tab" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="/static/85a426bb4392aabd111ef151615d868a/7321b/plugin-preferences.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Filiascreates.com%2Fstatic%2F85a426bb4392aabd111ef151615d868a%2Ff058b%2Fplugin-preferences.png" title="Kap plugin preferences tab" alt="Kap plugin preferences tab" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mac</category>
      <category>capture</category>
      <category>screen</category>
      <category>audio</category>
    </item>
  </channel>
</rss>
