<?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: Michael Uloth</title>
    <description>The latest articles on DEV Community by Michael Uloth (@ooloth).</description>
    <link>https://dev.to/ooloth</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F109453%2F728caebe-b450-40da-9816-833bd8299852.jpg</url>
      <title>DEV Community: Michael Uloth</title>
      <link>https://dev.to/ooloth</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ooloth"/>
    <language>en</language>
    <item>
      <title>Undoing a Merge to Your Main Git Branch</title>
      <dc:creator>Michael Uloth</dc:creator>
      <pubDate>Wed, 20 Dec 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/ooloth/undoing-a-merge-to-your-main-git-branch-421e</link>
      <guid>https://dev.to/ooloth/undoing-a-merge-to-your-main-git-branch-421e</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cAGXIedj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/ooloth/image/upload/c_scale%2Cf_auto%2Cq_auto%2Cw_1440/v1/mu/u-turn" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cAGXIedj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/ooloth/image/upload/c_scale%2Cf_auto%2Cq_auto%2Cw_1440/v1/mu/u-turn" alt="A yellow roadsign showing a 180-degree curve approach, indicating you'll be heading back the way you came." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You merge your PR only to discover you accidentally missed a bug and now prod is broken. Gah! Is there a quick way to undo that merge?&lt;/p&gt;

&lt;p&gt;Yes! What you need to do is “revert” your PR. Here’s how to do that using the GitHub UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reverting a PR
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;In the browser, go to your merged PR&lt;/li&gt;
&lt;li&gt;Click the “Revert” button near the bottom of the page to automatically create a new &lt;code&gt;revert-&lt;/code&gt; branch that reverses your changes&lt;/li&gt;
&lt;li&gt;When prompted, open a PR for that &lt;code&gt;revert-&lt;/code&gt; branch&lt;/li&gt;
&lt;li&gt;Merge the PR&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it. 😀 Crisis averted. Your main branch is back how it was before you merged your feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restoring your original feature branch
&lt;/h2&gt;

&lt;p&gt;If you want to debug and re-open an improved version of your original PR, here’s what you need to do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click the “Revert” button at the bottom of the &lt;em&gt;reversion&lt;/em&gt; PR you just merged to create a new &lt;code&gt;revert-revert-&lt;/code&gt; branch that includes the same changes as your original feature branch (by reverting the changes that reverted them)&lt;/li&gt;
&lt;li&gt;Pull that &lt;code&gt;revert-revert-&lt;/code&gt; branch locally (don’t open a PR yet) and make any changes you like to this new copy of your feature branch (not your old one!)&lt;/li&gt;
&lt;li&gt;When ready, push your changes and open a new PR for the &lt;code&gt;revert-revert-&lt;/code&gt; branch that contains the repaired version of your feature&lt;/li&gt;
&lt;li&gt;Merge when ready&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Reverting a commit
&lt;/h2&gt;

&lt;p&gt;So far, these steps have assumed the change that broke prod came from a PR. But what if you committed directly to &lt;code&gt;main&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;That’s a simpler fix. You’ll just need to revert that bad commit (no PR required):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout main
git log # find the hash of your bad commit ("q" to exit)
git revert HASH
git log # admire your new "Revert X" commit (optional)
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or using &lt;a href="https://github.com/jesseduffield/lazygit"&gt;Lazygit&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Branches panel and check out &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Go to the Commits panel and highlight the bad commit&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;t&lt;/code&gt; to revert your commit (you’ll see a new commit appear reversing your bad one)&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;P&lt;/code&gt; to push the new commit to your remote branch&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try, try again
&lt;/h2&gt;

&lt;p&gt;These steps have helped me out of multiple “oh 💩” moments at work. I hope they help you too!&lt;/p&gt;

&lt;p&gt;Reverting your reversion may seem a bit strange the first time you try it, but hopefully it will make sense as you think about it. 🙂&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/reverting-a-pull-request"&gt;Reverting a pull request&lt;/a&gt; • GitHub Docs 📚&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stackoverflow.com/questions/27852143/how-to-pr-and-merge-again-after-reverting-pr-using-github-revert-button"&gt;How to PR and merge again after reverting PR using Github Revert Button&lt;/a&gt; • StackOverflow 💬&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://justinjoyce.dev/undo-git-commit/"&gt;Undo a Git Commit&lt;/a&gt; • Justin Joyce 📖&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>github</category>
    </item>
    <item>
      <title>Switching Configs in Neovim</title>
      <dc:creator>Michael Uloth</dc:creator>
      <pubDate>Thu, 07 Sep 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/ooloth/switching-configs-in-neovim-haf</link>
      <guid>https://dev.to/ooloth/switching-configs-in-neovim-haf</guid>
      <description>&lt;p&gt;Learning how to configure &lt;a href="https://neovim.io/"&gt;Neovim&lt;/a&gt; can be overwhelming. One way to get started is to install a few pre-built configurations (like &lt;a href="https://www.lazyvim.org/"&gt;LazyVim&lt;/a&gt;, &lt;a href="https://nvchad.com/"&gt;NvChad&lt;/a&gt;, &lt;a href="https://astronvim.com/"&gt;AstroNvim&lt;/a&gt;, &lt;a href="https://www.lunarvim.org"&gt;LunarVim&lt;/a&gt; or the official Neovim &lt;a href="https://github.com/nvim-lua/kickstart.nvim"&gt;Kickstart&lt;/a&gt;) and see what you like.&lt;/p&gt;

&lt;p&gt;Most installation instructions &lt;a href="https://astronvim.com/#installation"&gt;will&lt;/a&gt; &lt;a href="https://www.lazyvim.org/installation"&gt;tell&lt;/a&gt; &lt;a href="https://nvchad.com/docs/quickstart/install"&gt;you&lt;/a&gt; to replace everything in your &lt;code&gt;~/.config/nvim&lt;/code&gt; directory with the new configuration. But once you do, you lose the ability to launch Neovim with your previous config.&lt;/p&gt;

&lt;p&gt;With that approach, you can only have one Neovim config installed at a time.  &lt;/p&gt;

&lt;p&gt;But what if you want to compare two configs? Or maintain different configs for different purposes (e.g. one for work and one for personal projects)?&lt;/p&gt;

&lt;h2&gt;
  
  
  Install each config in its own directory
&lt;/h2&gt;

&lt;p&gt;To be able to use more than one config, you'll need to make a couple changes to your setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Instead of installing a new configuration in &lt;code&gt;~/.config/nvim&lt;/code&gt;, install it in a custom &lt;code&gt;~/.config&lt;/code&gt; subdirectory&lt;/li&gt;
&lt;li&gt;Each time you open Neovim, specify which config you want by setting the &lt;code&gt;NVIM_APPNAME&lt;/code&gt; environment variable in your launch command&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, assuming you've installed &lt;a href="https://www.lazyvim.org/"&gt;LazyVim&lt;/a&gt; in &lt;code&gt;~/.config/nvim-lazyvim&lt;/code&gt;, you'd launch it with this command:&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;$ NVIM_APPNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nvim-lazyvim nvim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neovim uses &lt;code&gt;NVIM_APPNAME&lt;/code&gt; to determine which config directory to load. If you don't include it (or set it to an invalid value), Neovim will use the default config in &lt;code&gt;~/.config/nvim&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switching configs using &lt;code&gt;alias&lt;/code&gt;, &lt;code&gt;select&lt;/code&gt; or &lt;code&gt;fzf&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Lets assume your &lt;code&gt;~/.config&lt;/code&gt; directory includes these subdirectories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.config
├── nvim-astrovim
│ └── init.lua
├── nvim-kickstart
│ ├── init.lua
│ └── lua
│ ├── custom
│ └── kickstart
├── nvim-lazyvim
│ ├── init.lua
│ └── lua
│ ├── config
│ └── plugins
├── nvim-lunarvim
│ └── config.lua
└── nvim-nvchad
│ ├── init.lua
│ └── lua
│ ├── core
│ ├── custom
│ └── plugins
└── nvim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To quickly open Neovim using each config, you could create an &lt;code&gt;alias&lt;/code&gt; for each launch command:&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="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;v&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'nvim'&lt;/span&gt; &lt;span class="c"&gt;# default Neovim config&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;vz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'NVIM_APPNAME=nvim-lazyvim nvim'&lt;/span&gt; &lt;span class="c"&gt;# LazyVim&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;vc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'NVIM_APPNAME=nvim-nvchad nvim'&lt;/span&gt; &lt;span class="c"&gt;# NvChad&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;vk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'NVIM_APPNAME=nvim-kickstart nvim'&lt;/span&gt; &lt;span class="c"&gt;# Kickstart&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;va&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'NVIM_APPNAME=nvim-astrovim nvim'&lt;/span&gt; &lt;span class="c"&gt;# AstroVim&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;vl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'NVIM_APPNAME=nvim-lunarvim nvim'&lt;/span&gt; &lt;span class="c"&gt;# LunarVim&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use &lt;code&gt;select&lt;/code&gt; to list your configs so you can choose one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vv&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;select &lt;/span&gt;config &lt;span class="k"&gt;in &lt;/span&gt;lazyvim kickstart nvchad astrovim lunarvim
  &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nv"&gt;NVIM_APPNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nvim-&lt;span class="nv"&gt;$config&lt;/span&gt; nvim&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which would produce a menu like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hcrHTIeZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/ooloth/image/upload/c_scale%2Cf_auto%2Cq_auto%2Cw_1440/v1/mu/nvim-config-switcher-select" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hcrHTIeZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/ooloth/image/upload/c_scale%2Cf_auto%2Cq_auto%2Cw_1440/v1/mu/nvim-config-switcher-select" alt='A terminal after running the command "vv", showing the options "1) lazyvim 2) kickstart 3) nvchad 4) astrovim 5) lunarvim".' width="800" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or you could get fancy and use a fuzzy finder like &lt;a href="https://github.com/junegunn/fzf"&gt;fzf&lt;/a&gt; to do the same thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vv&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;# Assumes all configs exist in directories named ~/.config/nvim-*&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;fd &lt;span class="nt"&gt;--max-depth&lt;/span&gt; 1 &lt;span class="nt"&gt;--glob&lt;/span&gt; &lt;span class="s1"&gt;'nvim-*'&lt;/span&gt; ~/.config | fzf &lt;span class="nt"&gt;--prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Neovim Configs &amp;gt; "&lt;/span&gt; &lt;span class="nt"&gt;--height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~50% &lt;span class="nt"&gt;--layout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;reverse &lt;span class="nt"&gt;--border&lt;/span&gt; &lt;span class="nt"&gt;--exit-0&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;# If I exit fzf without selecting a config, don't open Neovim&lt;/span&gt;
  &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="nv"&gt;$config&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"No config selected"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="c"&gt;# Open Neovim with the selected config&lt;/span&gt;
  &lt;span class="nv"&gt;NVIM_APPNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; nvim
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what that &lt;code&gt;fzf&lt;/code&gt; menu would look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b_wPGGAJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/ooloth/image/upload/c_scale%2Cf_auto%2Cq_auto%2Cw_1440/v1/mu/nvim-config-switcher-fzf" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b_wPGGAJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/ooloth/image/upload/c_scale%2Cf_auto%2Cq_auto%2Cw_1440/v1/mu/nvim-config-switcher-fzf" alt='A terminal after running the command "vv", showing a menu with the options "nvim-lazyvim", "nvim-kickstart", "nvim-nvchad", "nvim-astrovim" and "nvim-lunarvim".' width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Configuring Neovim can be daunting, but being able to install a few configs and compare them is a great way to gather inspiration for your own custom config.&lt;/p&gt;

&lt;p&gt;Good luck!&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://neovim.io/doc/user/starting.html#%24NVIM_APPNAME"&gt;NVIM_APPNAME&lt;/a&gt; • Neovim docs 📚&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=LkHjJlSgKZY"&gt;Neovim Config Switcher&lt;/a&gt; • Elijah Manor 📺&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gist.github.com/elijahmanor/b279553c0132bfad7eae23e34ceb593b"&gt;Neovim Switcher Gist&lt;/a&gt; • Elijah Manor 👩‍💻&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://lazyman.dev/"&gt;Lazyman: Neovim Configuration Manager&lt;/a&gt; • Ronald Record 🛠️&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=6qSzFWRz6Ck"&gt;You Should Use a Neovim Distro If You Are New&lt;/a&gt; • ThePrimeagen 📺&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>neovim</category>
      <category>fzf</category>
    </item>
    <item>
      <title>Adding a Pull Request Template to Your GitHub Repo</title>
      <dc:creator>Michael Uloth</dc:creator>
      <pubDate>Thu, 15 Dec 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/ooloth/adding-a-pull-request-template-to-your-github-repo-1e22</link>
      <guid>https://dev.to/ooloth/adding-a-pull-request-template-to-your-github-repo-1e22</guid>
      <description>&lt;p&gt;If you spend time wondering what to write in the blank “Description” field every time you open a pull request, adding a PR template to your repo will speed you up.&lt;/p&gt;

&lt;p&gt;By pre-filling that “Description” field with a few quick prompts to answer, you’ll always know what to write and your reviewers will always get the info they need.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to add a PR template
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create a folder called &lt;code&gt;.github&lt;/code&gt; at the root of your repo&lt;/li&gt;
&lt;li&gt;Create a file called &lt;code&gt;.github/pull_request_template.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet"&gt;GitHub’s Markdown syntax&lt;/a&gt; to add any prompts you think will be useful to that file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it! Each time you open a pull request, the description section will now be pre-filled with your template.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to include in your template?
&lt;/h2&gt;

&lt;p&gt;Feel free to add any prompts you like! I prefer to keep it simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## ✅ What

&amp;lt;!-- A brief description of the changes in this PR. --&amp;gt;

## 🤔 Why

&amp;lt;!-- A brief description of the reason for these changes. --&amp;gt;

## 👩‍🔬 How to validate

&amp;lt;!-- Step-by-step instructions for how reviewers can verify these changes work as expected. --&amp;gt;

## 🔖 Related links

- [Jira task](&amp;lt;!-- link --&amp;gt;)
- [Slack thread](&amp;lt;!-- link --&amp;gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The comments are only visible while editing.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Don’t overdo it
&lt;/h2&gt;

&lt;p&gt;Whatever you do, don’t make your PR template too long. If you notice your team generally skips certain prompts, don’t hesitate to remove them.&lt;/p&gt;

&lt;p&gt;Only include prompts that actually save everyone time. If your reviewers consistently ask which ticket your PRs relate to, adding a ticket link prompt to the template is probably a good idea. But adding 50 well-intentioned checkboxes will only cause your team to skip the template entirely.&lt;/p&gt;

&lt;p&gt;So, keep it short. 😊&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository"&gt;Creating a pull request template for your repository&lt;/a&gt; • GitHub Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet"&gt;GitHub Markdown Cheatsheet&lt;/a&gt; • Adam Pritchard&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/get-started/writing-on-github"&gt;Writing on GitHub&lt;/a&gt; • Github Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://egghead.io/lessons/github-create-a-github-pr-template"&gt;Create a GitHub PR Template&lt;/a&gt; • egghead.io&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>github</category>
      <category>git</category>
      <category>pullrequest</category>
    </item>
    <item>
      <title>Dramatically Reducing Video File Size Using FFmpeg</title>
      <dc:creator>Michael Uloth</dc:creator>
      <pubDate>Thu, 10 Nov 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/ooloth/dramatically-reducing-video-file-size-using-ffmpeg-5g2</link>
      <guid>https://dev.to/ooloth/dramatically-reducing-video-file-size-using-ffmpeg-5g2</guid>
      <description>&lt;p&gt;Sometimes you want to upload a video, but you can’t because its file size is too large. When that happens, you can use &lt;code&gt;ffmpeg&lt;/code&gt; to shrink the video size with one command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps to shrink a video file
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;If you’re on a Mac and haven’t already, install &lt;a href="https://brew.sh/"&gt;Homebrew&lt;/a&gt;:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install &lt;code&gt;ffmpeg&lt;/code&gt; using Homebrew (or &lt;a href="https://ffmpeg.org/download.html"&gt;download ffmpeg&lt;/a&gt; for your OS):&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;ffmpeg&lt;/code&gt; on your file:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it — I was able to shrink a 10-minute macOS screencast from &lt;code&gt;1.4 GB&lt;/code&gt; to &lt;code&gt;148 MB&lt;/code&gt; just by running that command and waiting 6 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;p&gt;This approach works best when your final video quality doesn’t need to be very high.&lt;/p&gt;

&lt;p&gt;If you’re creating high-resolution content, you’ll want to dive deeper into &lt;code&gt;ffmpeg&lt;/code&gt;’s &lt;a href="https://ffmpeg.org/ffmpeg.html"&gt;configuration options&lt;/a&gt; or use a tool like &lt;a href="https://handbrake.fr/docs/en/1.5.0/"&gt;Handbrake&lt;/a&gt; to help you select the quality you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://linuxhint.com/how-reduce-video-size-with-ffmpeg/"&gt;How to Reduce Video Size With FFmpeg&lt;/a&gt; • John Otieno&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
    </item>
    <item>
      <title>The translateZ trick</title>
      <dc:creator>Michael Uloth</dc:creator>
      <pubDate>Thu, 01 Sep 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/ooloth/the-translatez-trick-13ao</link>
      <guid>https://dev.to/ooloth/the-translatez-trick-13ao</guid>
      <description>&lt;h2&gt;
  
  
  CSS filters are great
&lt;/h2&gt;

&lt;p&gt;CSS filters are super cool. They can take something that looks like this…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SU0AKpsb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/ooloth/image/upload/v1662044963/mu/css-filter-blur-0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SU0AKpsb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/ooloth/image/upload/v1662044963/mu/css-filter-blur-0.png" alt="The Firefox logo next to the CSS declaration ‘filter: blur(0)’." width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;…and make it look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5yQzXCYV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/ooloth/image/upload/v1662044970/mu/css-filter-blur-4px.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5yQzXCYV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/ooloth/image/upload/v1662044970/mu/css-filter-blur-4px.png" alt="A blurred Firefox logo next to the CSS declaration ‘filter: blur(4px)’." width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Love it. Effects like these are the fun part of writing CSS.&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;blur()&lt;/code&gt; is just one of many fun &lt;code&gt;filter&lt;/code&gt; options. The rest are listed &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/filter"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;CSS filters become especially useful when you need to animate items on and off the page, or in and out of the foreground. For example, you may want to blur an item more and more as it animates off the page. Or when a user opens a modal, you may want to gradually blur the page behind it to direct the user’s attention to the modal content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safari woes
&lt;/h2&gt;

&lt;p&gt;Unfortunately, once you start animating the CSS &lt;code&gt;filter&lt;/code&gt; property, you’re going to notice your animation is awfully choppy in Safari.&lt;/p&gt;

&lt;p&gt;I ran into this issue at work a couple years ago while animating CSS filters. And I recently heard Scott Tolinski lament on &lt;a href="https://twitter.com/stolinski/status/1532745802174578691"&gt;Twitter&lt;/a&gt; and the Syntax podcast that he was excited about using CSS filters but was forced to abandon them because of this Safari rendering issue.&lt;/p&gt;

&lt;p&gt;But there’s a fix! That’s why I’m writing this post, if only to help Scotty. 😎&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;To fix the issue in Safari, take this…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.thing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&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 change it to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.thing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateZ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* the fix */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. Your animations will run smoothly in Safari now.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does translateZ help?
&lt;/h2&gt;

&lt;p&gt;The basic reason &lt;code&gt;translateZ(ANY_VALUE)&lt;/code&gt; makes the animations run smoothly is that it tells Safari to render the animation using the GPU instead of the CPU. &lt;/p&gt;

&lt;p&gt;Without &lt;code&gt;translateZ&lt;/code&gt; (or a similar hint), Safari will try to animate your &lt;code&gt;filter&lt;/code&gt; using the CPU, which isn’t nearly as able to render complicated graphics. For graphics-intensive tasks like animating a filter effect, the GPU will always do a much better job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Help us out, Safari
&lt;/h2&gt;

&lt;p&gt;Chrome and other non-Safari browsers automatically hardware-accelerate animations like this by engaging the GPU, but Safari still requires you to add specific properties like &lt;code&gt;translateZ&lt;/code&gt; to get the same result.&lt;/p&gt;

&lt;p&gt;Hopefully the Safari team will fix that soon so Scott can animate all the cool CSS features he likes without resorting workarounds like this one. &lt;/p&gt;

&lt;p&gt;Until then, &lt;code&gt;translateZ&lt;/code&gt; is your friend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translateZ"&gt;translateZ&lt;/a&gt; | MDN Web Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.notion.so/The-filter-Boolean-trick-889b30c18eed4d71ba7d439fb4eb8f61"&gt;CSS filter&lt;/a&gt; | MDN Web Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.chrome.com/blog/css-filter-effects-landing-in-webkit/#support"&gt;CSS Filter Effects landing in WebKit&lt;/a&gt; — mentions need for &lt;code&gt;translateZ&lt;/code&gt; back in 2011 | Chrome Developers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.teamtreehouse.com/increase-your-sites-performance-with-hardware-accelerated-css"&gt;Increase Your Site’s Performance with Hardware-Accelerated CSS&lt;/a&gt; | Treehouse&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.notion.so/The-filter-Boolean-trick-889b30c18eed4d71ba7d439fb4eb8f61"&gt;Improving HTML5 App Performance with GPU Accelerated CSS Transitions&lt;/a&gt; | Urban Insight ********&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.smashingmagazine.com/2016/12/gpu-animation-doing-it-right/"&gt;CSS GPU Animation: Doing It Right&lt;/a&gt; | Smashing Magazine&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Fundamentals#specific_coding_tips_for_application_performance"&gt;Web Performance Fundamentals&lt;/a&gt; — calls out that &lt;code&gt;translateZ&lt;/code&gt; is still needed to get hardware accelerated CSS animations on some platforms | MDN Web Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/andyngo/status/1263056084719202304?s=20"&gt;Andy Ngo tweet&lt;/a&gt; — suggests &lt;code&gt;translate3d&lt;/code&gt; version of same trick | Twitter&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>css</category>
      <category>safari</category>
    </item>
    <item>
      <title>What is a Factory Function?</title>
      <dc:creator>Michael Uloth</dc:creator>
      <pubDate>Sun, 27 Feb 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/ooloth/what-is-a-factory-function-c6l</link>
      <guid>https://dev.to/ooloth/what-is-a-factory-function-c6l</guid>
      <description>&lt;p&gt;I hadn’t heard of the factory function technique until I read &lt;a href="https://kyleshevlin.com/what-is-a-factory-function"&gt;What is a Factory Function?&lt;/a&gt; by Kyle Shevlin:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A factory function is a function that returns a new object. The key feature of a factory is that its only job is to &lt;em&gt;pump out those items&lt;/em&gt;, just like an actual factory.&lt;/p&gt;

&lt;p&gt;The place I use factories most often is when I want to return an object with methods but use closures to create private methods and values. Even better, I &lt;em&gt;never&lt;/em&gt; have to even think about the &lt;code&gt;this&lt;/code&gt; keyword.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, you can write them instead of classes!&lt;/p&gt;

&lt;p&gt;I found a good use case for this technique today while adding extra properties and helper methods to blocks I’d fetched from the Notion API, so I wanted to give Kyle a shout-out for the timely assist.&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://kyleshevlin.com/all-posts"&gt;Kyle’s blog&lt;/a&gt; for lots of other interesting coding tips.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Writing Great Alt Text</title>
      <dc:creator>Michael Uloth</dc:creator>
      <pubDate>Thu, 17 Feb 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/ooloth/writing-great-alt-text-5apa</link>
      <guid>https://dev.to/ooloth/writing-great-alt-text-5apa</guid>
      <description>&lt;p&gt;I agree with Jake Archibald's argument in &lt;a href="https://jakearchibald.com/2021/great-alt-text/"&gt;Writing great alt text: Emotion matters&lt;/a&gt; that when we write image alt text, we should aim to evoke the same emotions as the image visuals do. Otherwise, screen reader users miss out.&lt;/p&gt;

&lt;p&gt;If an image makes you laugh or cry, ideally the alt text will do the same:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The relevant parts of an image aren't limited to the cold hard facts. Images can make you feel a particular way, and that's something that should be made available to a screen reader user.&lt;/p&gt;

&lt;p&gt;It isn't just a head-shot of me. I'm doing a thing. I'm peering from behind a plant and pulling a bit of a silly face. There's humour expressed in the image. I'm not saying that it's going to win any comedy awards, but the image expresses a particular tone, and that matters. So, it should go in the alt: “Head-shot of me, a pale white guy, wearing glasses, grinning slightly, and partially hiding behind a plant”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let the creative writing begin!&lt;/p&gt;

</description>
      <category>html</category>
      <category>a11y</category>
      <category>images</category>
    </item>
    <item>
      <title>Levels of Abstraction in Testing</title>
      <dc:creator>Michael Uloth</dc:creator>
      <pubDate>Tue, 15 Feb 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/ooloth/levels-of-abstraction-in-testing-3mm2</link>
      <guid>https://dev.to/ooloth/levels-of-abstraction-in-testing-3mm2</guid>
      <description>&lt;p&gt;Sam Selikoff's &lt;a href="https://youtube.com/watch?v=G_0yKeh0Sf0"&gt;Levels of abstraction in testing&lt;/a&gt; does a nice job demonstrating how to make unit tests easier to read by abstracting the low level setup steps at the beginning of each test into a well-named function.&lt;/p&gt;

&lt;p&gt;After that change, the everything in the tests reads at a single level of abstraction (namely, how would a user interact with this component?) rather than alternating between what the computer is doing and what the user is doing. The result is easier to understand.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Basically, you just want to act as if you’re sitting next to the person who is using the system you’re testing…just imagine how you’d explain to them what behaviour you’re testing and then write your tests using similar words.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think this advice would apply just as well to non-test code. If you notice a code block mixes levels of abstraction, try extracting the lower level bits so the resulting code reads as a continuous train of thought from a single point of view.&lt;/p&gt;

&lt;p&gt;Check out Sam's complete video:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/G_0yKeh0Sf0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>testing</category>
    </item>
    <item>
      <title>How ecobee Uses Slack to Report Data Entry Errors to Content Editors</title>
      <dc:creator>Michael Uloth</dc:creator>
      <pubDate>Mon, 31 May 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/ooloth/how-ecobee-uses-slack-to-report-data-entry-errors-to-content-editors-nh3</link>
      <guid>https://dev.to/ooloth/how-ecobee-uses-slack-to-report-data-entry-errors-to-content-editors-nh3</guid>
      <description>&lt;p&gt;&lt;em&gt;This post originally appeared on the &lt;a href="https://www.ecobee.dev/blog/2021-05-21-how-ecobee-uses-slack-to-report-data-entry-errors-to-content-editors/"&gt;ecobee engineering blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My team at ecobee spent months debugging build failures on the staging site for &lt;a href="https://www.ecobee.com/"&gt;ecobee.com&lt;/a&gt; until we finally discovered a way to solve them with automated &lt;a href="https://slack.com"&gt;Slack&lt;/a&gt; notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production vs. staging
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.ecobee.com/"&gt;ecobee.com&lt;/a&gt; is an e-commerce website, which we populate with content from &lt;a href="https://www.contentful.com"&gt;Contentful&lt;/a&gt; and &lt;a href="https://www.shopify.com"&gt;Shopify&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our designers and writers create new pages by building and assembling “blocks” of content in Contentful. As they work, they can preview their changes on a staging version of our site.&lt;/p&gt;

&lt;p&gt;Here’s the live version of ecobee.com:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6EcHSaKx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n5j9kqtxncoljrxzostm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6EcHSaKx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n5j9kqtxncoljrxzostm.png" alt="The top section of ecobee.com, showing an image of a Smart Camera, Smart Thermostat, and two Smart Sensors next to the words, 'Imagine what could be'." width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here’s the staging version of ecobee.com:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U30ihuQ5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9kr0ysw893c382brl7ds.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U30ihuQ5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9kr0ysw893c382brl7ds.png" alt="An identical-looking screenshot of the top section of the staging version of ecobee.com, showing an image of a Smart Camera, Smart Thermostat, and two Smart Sensors next to the words, 'Imagine what could be'." width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The staging version looks very similar to the live version because, well, that’s the point. It’s generated using the exact same codebase as the live site and all the content for both versions of the site come from Contentful.&lt;/p&gt;

&lt;p&gt;If you aren't familiar, here’s what Contentful looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6AzP7Nna--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s8vvbyva9zz1xi6si0n2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6AzP7Nna--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s8vvbyva9zz1xi6si0n2.png" alt="A screenshot of a Contentful entry showing pairs of input fields and their labels, like 'Title', 'URL', etc." width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This example is the “Page” entry that creates the homepage for ecobee.com. It’s basically a series of fields: some are required; some are not.&lt;/p&gt;

&lt;p&gt;If you mark a field as required, Contentful will confirm that field is populated before it lets you publish the entry. (The same applies to other field validations like max length, number vs. text, etc.) And because the live site only consumes published entries, we know all the fields have been validated (e.g. no required fields are empty), and the data the website receives will match what we expect.&lt;/p&gt;

&lt;p&gt;So far, so good.&lt;/p&gt;

&lt;p&gt;However, the staging site is a little different: it connects to the same production Contentful data as the live site, but also includes any draft entries that have not yet been published.&lt;/p&gt;

&lt;p&gt;That’s by design, so that editors can preview their in-progress changes on staging as they go.&lt;/p&gt;

&lt;p&gt;But those draft entries have one major problem: their fields have not been validated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Broken staging builds
&lt;/h2&gt;

&lt;p&gt;The challenge we faced was that we kept seeing errors like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--niFfjhoN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xopunlopycgji0rpybk8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--niFfjhoN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xopunlopycgji0rpybk8.png" alt="The console output of a failed website build showing an error in red that says 'Cannot read property match of null'." width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This the deploy log for a build of our staging website on &lt;a href="https://www.netlify.com"&gt;Netlify&lt;/a&gt;. And over and over, we encountered errors saying &lt;code&gt;Cannot read property X of null&lt;/code&gt; or &lt;code&gt;Cannot read property X of undefined&lt;/code&gt;, which indicated that our codebase trusted a value existed (because its Contentful field was required) and tried to use it, but failed because that value was unexpectedly empty.&lt;/p&gt;

&lt;p&gt;So, why didn’t Contentful make sure those required fields were populated?&lt;/p&gt;

&lt;h2&gt;
  
  
  Draft entries cannot be trusted
&lt;/h2&gt;

&lt;p&gt;The basic reason is that unpublished draft entries (which staging consumes) are not validated by Contentful because Contentful only applies validations when an entry is published.&lt;/p&gt;

&lt;p&gt;So, while our production builds were stable (since the live site only consumes validated, published entries), our staging builds were constantly exposed to the risk that a field coming from a draft entry could contain invalid data (or no data at all).&lt;/p&gt;

&lt;p&gt;This issue was minor when our team only had one or two content editors. But as our team grew to include half a dozen or more editors working separately and constantly triggering new builds, incomplete data began to be hit staging on a daily basis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging in the dark
&lt;/h2&gt;

&lt;p&gt;When one of these errors occurred, the staging website would stop updating, and one or more devs would have to put aside their work and jump into the build logs to locate an error like the one shown above.&lt;/p&gt;

&lt;p&gt;Next came the hard part: trying to figure out which field in Contentful was responsible for the problem.&lt;/p&gt;

&lt;p&gt;For example, all the error in the screenshot above says is that the &lt;code&gt;Link&lt;/code&gt; component was passed an empty &lt;code&gt;href&lt;/code&gt;. It doesn’t say which field in Contentful that empty value came from.&lt;/p&gt;

&lt;p&gt;Trying to track down those empty fields was no fun. Each hunt took up a lot of time and involved a lot of head scratching and rooting through Contentful, searching for the root of each problem. Work ground to a halt for editors and developers while we hunted, sometimes for up to an hour.&lt;/p&gt;

&lt;p&gt;We were to determined to free ourselves from these thankless debugging sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom null checks everywhere
&lt;/h2&gt;

&lt;p&gt;To do that, we started adding defensive &lt;code&gt;null&lt;/code&gt; checks all over our codebase.&lt;/p&gt;

&lt;p&gt;Even though we didn’t have to worry about the existence of required values in production, for the sake of our editors having a working preview site to look at, we realized we needed to treat required fields as unreliable and start guarding against their lack of existence.&lt;/p&gt;

&lt;p&gt;So, we started adding things like this all over our codebase that intercepted empty values and identified exactly which Contentful field to fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ReviewsResolver&lt;/span&gt; &lt;span class="o"&gt;=&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;productReference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;header&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="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;BlockResolverProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ReviewsDataType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;logError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useLogError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;productReference&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;logError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`The Reviews entry named "&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="s2"&gt;" cannot be displayed because its required field "Product reference" is empty.`&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="kc"&gt;null&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Reviews&lt;/span&gt; &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;productReference&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Slack to the rescue
&lt;/h2&gt;

&lt;p&gt;We also created a helper, called &lt;code&gt;logError&lt;/code&gt;, to decide whether each error message should appear just in the console or also be posted to Slack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// If this is prod, report the issue and break the build&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PRODUCTION&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prodImpact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The live site will not update until this issue is resolved.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prodMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`🚨 PRODUCTION ERROR 🚨\n\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prodImpact&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;postToSlack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prodMessage&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prodMessage&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;// If this is staging, report the issue and continue the build&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;STAGING&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stagingImpact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The staging site will continue to update while this issue gets resolved.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stagingMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`⚠️ STAGING ERROR ⚠️\n\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;stagingImpact&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;postToSlack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stagingMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stagingMessage&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="c1"&gt;// In all other environments, just log the issue and continue the build&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formatMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\n\n⚠️ &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\n`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, we ask, “Are we on the production site? Or staging? Or just in development?”, and based on the answer we decide if we should to stop the build (production only) and/or post the error to Slack (production and staging only). In all environments, we also log the error to the console.&lt;/p&gt;

&lt;p&gt;We then created a dedicated Slack channel and invited all our editors to it, created a &lt;a href="https://slack.com/intl/en-ca/help/articles/115005265703-Create-a-bot-for-your-workspace"&gt;Slack bot&lt;/a&gt;, and used the &lt;a href="https://slack.dev/node-slack-sdk/web-api"&gt;Slack Web API&lt;/a&gt; to automatically post these Slack messages for us:&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="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebClient&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="s1"&gt;@slack/web-api&lt;/span&gt;&lt;span class="dl"&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;isTest&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="s1"&gt;../../src/utils/getEnv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isSSR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;postToSlack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#dotcom-cms-errors&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;// Only post during a build that isn't part of a test run&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isSSR&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;isTest&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slackClient&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slackClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\n[postToSlack]: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n`&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, whenever a build error occurs and is caused by a Contentful data entry problem we know about, the message goes straight to that editor-focused channel so they can can hop into Contentful and fix the issue without dev assistance:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZF-OUfds--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1knyqlw6zd3238zzjtlr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZF-OUfds--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1knyqlw6zd3238zzjtlr.png" alt="A message posted to Slack by 'DotCom Build Alerts' explaining that a specific 'Reviews' entry cannot be shown because one of its required fields is empty." width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  An endless process
&lt;/h2&gt;

&lt;p&gt;But, we weren’t done.&lt;/p&gt;

&lt;p&gt;We started noticing that after we had fixed the 10 or so errors we had seen over and over, there were 10 or so new ones that we hadn’t previously seen because they never had a chance to appear because the other errors were happening first.&lt;/p&gt;

&lt;p&gt;It slowly dawned on us that with our current approach of inserting custom &lt;code&gt;null&lt;/code&gt; checks and error messages in individual components, this problem would never end. We’d have to &lt;code&gt;null&lt;/code&gt; check every single value we considered required and continue doing that whenever we wrote new code.&lt;/p&gt;

&lt;p&gt;That was not going to be scalable for us in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated validation step
&lt;/h2&gt;

&lt;p&gt;So, we came up with a better idea.&lt;/p&gt;

&lt;p&gt;To avoid having to spread &lt;code&gt;null&lt;/code&gt; checks all over our codebase and worry about this forever, early in our build we now query all of the data from Contentful that our website will use along with the rules for every field, and then compare the two.&lt;/p&gt;

&lt;p&gt;We take each field and its rules and pass it to a function that asks if it’s invalid. For example, for requiredness, we check if the field’s rules say it’s required and if its value is empty. If both are true at the same time, the field is invalid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;isEmptyRequiredField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fieldRules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ContentFields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fieldValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;isRequired&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;fieldRules&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;fieldRules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;

  &lt;span class="c1"&gt;// We don't want to check falsiness here, since that would flag fields intentionally set to 0&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isEmpty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fieldValue&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isRequired&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isEmpty&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;detectFieldInvalidity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ContentTypeIdsAndFields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;contentTypeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fieldName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fieldValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;fieldRules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getFieldRules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contentTypeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fieldName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isEmptyRequiredField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fieldRules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fieldValue&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="s1"&gt;is required but empty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isInvalidRichTextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fieldValue&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="s1"&gt;contains invalid HTML&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we have a single place where we call our &lt;code&gt;logError&lt;/code&gt; helper as many times as necessary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isInvalidReason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;detectFieldInvalidity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contentTypeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fieldName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fieldName&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isInvalidReason&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;contentTypeName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;startCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contentTypeId&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;entryName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entry&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="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentTypeAndEntryName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entryName&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`The Contentful &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;contentTypeName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; entry named "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;entryName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`An unnamed Contentful &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;contentTypeName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; entry with entry ID "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentful_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;node_locale&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node_locale&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-us&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-ca&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="kc"&gt;undefined&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&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;contentTypeAndEntryName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; cannot be displayed because its field "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fieldName&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;isInvalidReason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.\n\nPlease update this field and publish your changes.`&lt;/span&gt;

  &lt;span class="nx"&gt;logError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentful_id&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 updated version of these Slack error notifications looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PhQ5ek_d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c3r64smjviobiv67172y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PhQ5ek_d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c3r64smjviobiv67172y.png" alt="Another message posted to Slack by 'DotCom Build Alerts' explaining that a specific 'Reviews' entry cannot be shown because one of its required fields is empty, this time including a link to the broken entry." width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The updated template tells editors the name of the broken Contentful entry, which field in the entry has the problem, what the problem is, how to fix it, a link to the entry, and context about the environment where the build error occurred.&lt;/p&gt;

&lt;h2&gt;
  
  
  Happy editors and developers
&lt;/h2&gt;

&lt;p&gt;We've been living with this solution for the past few months and our editors and developers are both much happier!&lt;/p&gt;

&lt;p&gt;There are no more confusing debugging sessions. Whenever a staging build breaks, the solution is posted to Slack a second later and an editor can fix it themselves within a minute.&lt;/p&gt;

&lt;p&gt;This has eliminated an annoying source context switching and restored hours of developer and editor productivity each week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Please share your ideas!
&lt;/h2&gt;

&lt;p&gt;So, that’s how the "dotcom" team at ecobee is currently tackling the challenge of using the same codebase for production and staging but sending that codebase different data in each environment.&lt;/p&gt;

&lt;p&gt;If you’ve encountered this issue before and have tips or best practices that might help us, please let us know! We realize this is likely a common problem for sites with a content management system, so we’d love to hear how you approached solving this issue.&lt;/p&gt;

</description>
      <category>slack</category>
      <category>contentful</category>
      <category>netlify</category>
      <category>gatsby</category>
    </item>
    <item>
      <title>The filter(Boolean) trick</title>
      <dc:creator>Michael Uloth</dc:creator>
      <pubDate>Tue, 02 Jun 2020 13:06:08 +0000</pubDate>
      <link>https://dev.to/ooloth/the-filter-boolean-trick-2o55</link>
      <guid>https://dev.to/ooloth/the-filter-boolean-trick-2o55</guid>
      <description>&lt;p&gt;Here’s a trick I often find helpful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bad array. Very, very bad.
&lt;/h2&gt;

&lt;p&gt;You have an array of whatever:&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;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;stuff&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;moreStuff&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;But hiding in that array are some unusable &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;undefined&lt;/code&gt; values:&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;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;good&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="nx"&gt;great&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those empty values might be a sneaky little gift from your API. Or you may have left them there yourself while validating the original data. Either way, you’ve got a problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looping over null data
&lt;/h2&gt;

&lt;p&gt;If you try to perform actions on every item in the array, you’ll run into errors when you hit those &lt;code&gt;null&lt;/code&gt; and &lt;code&gt;undefined&lt;/code&gt; items:&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;newArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Of course this will work, wheeee...&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assumption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;thing&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Oh noooo...&lt;/span&gt;
&lt;span class="err"&gt;🚨&lt;/span&gt; &lt;span class="nx"&gt;Cannot&lt;/span&gt; &lt;span class="nx"&gt;read&lt;/span&gt; &lt;span class="nx"&gt;property&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;thing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="err"&gt;🚨&lt;/span&gt;
&lt;span class="err"&gt;🚨&lt;/span&gt; &lt;span class="nx"&gt;Cannot&lt;/span&gt; &lt;span class="nx"&gt;read&lt;/span&gt; &lt;span class="nx"&gt;property&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;thing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="err"&gt;🚨&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Illegal! Now you’re a criminal. Before you interact with an item, you need to make sure it exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Null checks?
&lt;/h2&gt;

&lt;p&gt;You could confirm each item exists by performing a &lt;code&gt;null&lt;/code&gt; check before you use 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;newArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Life has made me cautious.&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;item&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;item&lt;/span&gt; &lt;span class="c1"&gt;// Just forget it&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// If we get this far, item exists.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assumption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;thing&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Buuut, now your code is getting cluttered. And worse, those dangerous empty values will be passed along to &lt;code&gt;newArray&lt;/code&gt;. So when &lt;code&gt;newArray&lt;/code&gt; is used, another round of suspicious &lt;code&gt;null&lt;/code&gt; checks will be needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The truth and only the truth
&lt;/h2&gt;

&lt;p&gt;Want something better?&lt;/p&gt;

&lt;p&gt;Here’s my favourite way to quickly remove all empty items from an array:&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;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;good&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="nx"&gt;great&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;truthyArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// truthyArray = [{ good }, { great }]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;filter(Boolean)&lt;/code&gt; step does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Passes each item in the array to the &lt;code&gt;Boolean()&lt;/code&gt; object&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Boolean()&lt;/code&gt; object &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Type_coercion"&gt;coerces&lt;/a&gt; each item to &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt; depending on whether it’s &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Truthy"&gt;truthy&lt;/a&gt; or &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Falsy"&gt;falsy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;If the item is truthy, we keep it&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Where did item go?
&lt;/h2&gt;

&lt;p&gt;I love how concise &lt;code&gt;filter(Boolean)&lt;/code&gt; is, but it might look strange that we aren’t explicitly passing &lt;code&gt;item&lt;/code&gt; to &lt;code&gt;Boolean&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The main thing to know is that, in JavaScript, this:&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;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is exactly the same as this:&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;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second version is just written in a &lt;a href="https://en.wikipedia.org/wiki/Tacit_programming"&gt;“tacit” or “point-free” style&lt;/a&gt;. We don’t name each item and pass it into &lt;code&gt;Boolean&lt;/code&gt;, but JavaScript understands that &lt;code&gt;Boolean&lt;/code&gt; takes one argument, so it takes the argument &lt;code&gt;filter()&lt;/code&gt; exposes and &lt;a href="https://mostly-adequate.gitbooks.io/mostly-adequate-guide/ch02.html"&gt;passes it to Boolean for you&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you find the first version easier to understand, use it! Writing readable code is more important than using fancy tricks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safer mapping
&lt;/h2&gt;

&lt;p&gt;With our new tool, we can remove the &lt;code&gt;null&lt;/code&gt; checks from above and chain a filtering step instead:&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;newArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Item is always truthy!&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assumption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;thing&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, our &lt;code&gt;map&lt;/code&gt; can focus on what it’s trying to do, and we’ve removed the empty values from our pipeline forever.&lt;/p&gt;

&lt;p&gt;Hope that helps!&lt;/p&gt;

&lt;h2&gt;
  
  
  Glossary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Falsy values:&lt;/strong&gt; &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;-0&lt;/code&gt;, &lt;code&gt;0n&lt;/code&gt;, &lt;code&gt;""&lt;/code&gt;, &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;undefined&lt;/code&gt;, &lt;code&gt;NaN&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Truthy values:&lt;/strong&gt; anything not in the falsy list&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter"&gt;Filter&lt;/a&gt; | MDN web docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean"&gt;Boolean&lt;/a&gt; | MDN web docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Falsy"&gt;Falsy&lt;/a&gt; | MDN web docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Truthy"&gt;Truthy&lt;/a&gt; | MDN web docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Type_coercion"&gt;Type coercion&lt;/a&gt; | MDN web docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Tacit_programming"&gt;Tacit programming&lt;/a&gt; | Wikipedia&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mostly-adequate.gitbooks.io/mostly-adequate-guide/ch02.html"&gt;Chapter 2, Professor Frisby’s Mostly Adequate Guide to Functional Programming&lt;/a&gt; | Brian Lonsdorf&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.michaeluloth.com/filter-boolean"&gt;michaeluloth.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>array</category>
      <category>filter</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Using GraphQL with Gatsby</title>
      <dc:creator>Michael Uloth</dc:creator>
      <pubDate>Mon, 03 Jun 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/ooloth/up-running-with-gatsby-10-using-graphql-with-gatsby-1c87</link>
      <guid>https://dev.to/ooloth/up-running-with-gatsby-10-using-graphql-with-gatsby-1c87</guid>
      <description>&lt;p&gt;This is the tenth video in our &lt;a href="https://www.youtube.com/watch?v=jAa1wh5ATm0&amp;amp;list=PLHBEcHVSROXQQhXpNhmiVKKcw72Cc0V-U"&gt;beginner series&lt;/a&gt; exploring GatsbyJS and how to use it to easily build performant apps and websites.&lt;/p&gt;

&lt;p&gt;In this video, we explore how GraphQL works and practice using it with Gatsby to query our content.&lt;/p&gt;

&lt;p&gt;Check out the video below or &lt;a href="https://www.youtube.com/watch?v=jAa1wh5ATm0&amp;amp;list=PLHBEcHVSROXQQhXpNhmiVKKcw72Cc0V-U"&gt;view the entire playlist&lt;/a&gt; on YouTube. &lt;/p&gt;

&lt;p&gt;Enjoy! 🎉📺&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/IaorT4-efuU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>react</category>
      <category>graphql</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Adding Content to a Gatsby Project</title>
      <dc:creator>Michael Uloth</dc:creator>
      <pubDate>Mon, 27 May 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/ooloth/up-running-with-gatsby-9-adding-content-to-a-gatsby-project-57jl</link>
      <guid>https://dev.to/ooloth/up-running-with-gatsby-9-adding-content-to-a-gatsby-project-57jl</guid>
      <description>&lt;p&gt;This is the ninth video in our &lt;a href="https://www.youtube.com/watch?v=jAa1wh5ATm0&amp;amp;list=PLHBEcHVSROXQQhXpNhmiVKKcw72Cc0V-U"&gt;beginner series&lt;/a&gt; exploring GatsbyJS and how to use it to easily build performant apps and websites.&lt;/p&gt;

&lt;p&gt;In this video, we explore a few ways we can add content to a Gatsby project and discuss the advantages and disadvantages of each approach.&lt;/p&gt;

&lt;p&gt;Check out the video below or &lt;a href="https://www.youtube.com/watch?v=jAa1wh5ATm0&amp;amp;list=PLHBEcHVSROXQQhXpNhmiVKKcw72Cc0V-U"&gt;view the entire playlist&lt;/a&gt; on YouTube. &lt;/p&gt;

&lt;p&gt;Enjoy! 🎉📺&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/MPqEU9ywgW8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>react</category>
      <category>webpack</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
