<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>The BHolmesDev Blog</title><description>A smattering of posts about web development by Ben Holmes</description><link>https://bholmes.dev/</link><item><title>Build a Self-Updating LLM Knowledge Base</title><link>https://bholmes.dev/blog/llm-knowledge-bases/</link><guid isPermaLink="true">https://bholmes.dev/blog/llm-knowledge-bases/</guid><description>Have agents tag, backlink, and compound your markdown notes into wikis and visualizations, all on a schedule.</description><pubDate>Mon, 06 Jul 2026 12:00:00 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;You may have seen Andrej Karpathy talk about &lt;a href=&quot;https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f&quot;&gt;&quot;LLM Wikis.&quot;&lt;/a&gt; They turn your unorganized thoughts into easy-to-navigate notes, wikis, or visualizations. It&apos;s an incredibly powerful tool for researchers, software developers, scattered notetakers... basically everyone. Here&apos;s the system to have agents organize your second brain automatically:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Start taking notes as markdown&lt;/li&gt;&lt;li&gt;Add an &lt;strong&gt;/enrich-note&lt;/strong&gt; skill that instructs agents to add tags, sources, and backlinks&lt;/li&gt;&lt;li&gt;Add a &lt;strong&gt;wiki&lt;/strong&gt; skill that compounds notes into a persistent knowledge base&lt;/li&gt;&lt;li&gt;Create automations that run these skills on a schedule&lt;/li&gt;&lt;li&gt;Add visualizations built live from your notes&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&apos;s go set this up.&lt;/p&gt;&lt;h2&gt;Step 1: one directory of raw markdown&lt;/h2&gt;&lt;p&gt;We&apos;re going to take our notes as Markdown to make them easily accessible to agents. You &lt;em&gt;can&lt;/em&gt; use &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt; for this, or if you want a friendlier app that feels more like Apple Notes, I built one: &lt;a href=&quot;https://hubble.md/&quot;&gt;Hubble.md&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Following &lt;a href=&quot;https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f&quot;&gt;Andrej Karpathy&apos;s recommendation&lt;/a&gt;, we&apos;ll put all of our handwritten (or voice-transcribed) notes in a &lt;code&gt;raw/&lt;/code&gt; directory. These files are raw, unfiltered thoughts, for our agents to work with.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;raw/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── rick-rubin-s-philosophy-of-simplicity.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── nvidia-blackwell-architecture.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── insights-from-sell-like-steve-jobs.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── acquired-pod-ferrari.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here&apos;s a sample I rambled out using a voice transcription tool after listening to &lt;a href=&quot;https://www.acquired.fm/episodes/ferrari&quot;&gt;Acquired&apos;s Ferrari episode&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Ferrari (Acquired)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;It was super interesting how they described Ferrari as a brand, where&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;it&apos;s both a combination of a luxury brand, like a Hermes or a Gucci,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;but also a sports team, which explains why they can put their logo on&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;everything down to an Acer laptop...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note there&apos;s no metadata and no backlinks. Honestly, you don&apos;t even need the header. The agent will handle those details for you.&lt;/p&gt;&lt;h2&gt;Step 2: the /enrich-note skill&lt;/h2&gt;&lt;p&gt;Let&apos;s have agents enrich these notes with details for easy retrieval. I wrote the &lt;a href=&quot;https://github.com/bholmesdev/llm-knowledge-base-skills/blob/main/skills/enrich-note/SKILL.md&quot;&gt;/enrich-note&lt;/a&gt; skill to do this. You can grab it (and every other skill in this post) with one command:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; skills&lt;/span&gt;&lt;span&gt; add&lt;/span&gt;&lt;span&gt; bholmesdev/llm-knowledge-base-skills&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The skill does three things:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;1. Tags:&lt;/strong&gt; The skill maintains a shared tag registry as a &lt;a href=&quot;https://github.com/bholmesdev/llm-knowledge-base-skills/blob/main/skills/enrich-note/references/tags.md&quot;&gt;&lt;code&gt;tags.md&lt;/code&gt; file&lt;/a&gt; listing every tag with a one-line description. I suggest tags relating to the medium of the source (blog, podcast, video) and others relating to topics / areas of interest (&lt;code&gt;ai-hardware&lt;/code&gt;, &lt;code&gt;business&lt;/code&gt;, &lt;code&gt;rust&lt;/code&gt;). The agent is also told to reuse existing tags and be &lt;em&gt;reluctant&lt;/em&gt; to coin new ones. Otherwise, I&apos;ve found agents invent tags too liberally.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2. Sources:&lt;/strong&gt; If the note came from somewhere (a podcast, a book, a video) the agent does some web research to find the source URL and records it in frontmatter.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3. Related notes:&lt;/strong&gt; The agent greps the rest of the vault for genuinely related notes and adds them as wikilinks in a &lt;code&gt;## Related&lt;/code&gt; section. This can be an expensive operation, but no more expensive than asking an agent to complete a coding task in a small-to-medium-sized codebase. I personally have hundreds of notes. If you test this on a larger corpus let me know what you&apos;ve learned or optimizations you&apos;ve found.&lt;/p&gt;&lt;p&gt;After a pass, that Acquired podcast note on Ferrari looks like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;tags: [podcast, business, branding]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;source: &quot;Acquired podcast — \&quot;Ferrari\&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;url: &quot;https://www.acquired.fm/episodes/ferrari&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;enrichedAt: &quot;2026-06-27T23:40:26Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Ferrari (Acquired)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## Related&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [[raw/walt-disney.md|Walt Disney on Acquired]]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note the &lt;code&gt;enrichedAt&lt;/code&gt; stamp. That&apos;s how an automation knows which notes have been enriched in the past. Idempotency matters when we start running this skill on a schedule.&lt;/p&gt;&lt;h2&gt;Step 3: the wiki skill&lt;/h2&gt;&lt;p&gt;Enriched notes are searchable, but they&apos;re still fragments. The next layer up is a &lt;strong&gt;wiki&lt;/strong&gt;: a persistent, interlinked knowledge base that an agent builds from your notes.&lt;/p&gt;&lt;p&gt;This comes directly from Karpathy&apos;s &lt;a href=&quot;https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f&quot;&gt;llm-wiki gist&lt;/a&gt;, which I&apos;d recommend reading in full. The core idea: have an agent generate a frontpage around an area of interest (in my case, coding agent tips or religious studies) that is easy-to-navigate.&lt;/p&gt;&lt;p&gt;This is useful for &lt;em&gt;you&lt;/em&gt; since it creates an easy-to-scan index of people, places, and things. But it&apos;s also useful for &lt;em&gt;agents&lt;/em&gt; to navigate whenever you ask questions relating to your notes. Instead of RAG-style retrieval where the LLM rediscovers your knowledge from scratch on every question, the agent maintains a compounding artifact: entity pages, topic syntheses, an index, a log. As Andrej puts it: &quot;the LLM is the programmer; the wiki is the codebase.&quot;&lt;/p&gt;&lt;p&gt;To get started, paste the gist into your agent and add a sentence about what you want. Something like:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Read this: &lt;a href=&quot;https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f&quot;&gt;https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f&lt;/a&gt;. Set up a wiki in &lt;code&gt;wikis/latest-ai/&lt;/code&gt; that tracks coding agent tips from my notes - model prompting, orchestration patterns, useful agent skills, etc. Seed it from anything relevant in &lt;code&gt;raw/&lt;/code&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;The agent scaffolds the directory, writes its own &lt;code&gt;AGENTS.md&lt;/code&gt; schema with wiki maintenance instructions, and ingests your existing notes.&lt;/p&gt;&lt;p&gt;From then on, I wrote a skill to &quot;refresh&quot; that wiki over time: &lt;a href=&quot;https://github.com/bholmesdev/llm-knowledge-base-skills/blob/main/skills/refresh-wiki/SKILL.md&quot;&gt;/refresh-wiki&lt;/a&gt;. This looks for new or changed notes in &lt;code&gt;raw/&lt;/code&gt;, ingests the relevant ones into each wiki, updates entity and concept pages, and runs a lint pass for contradictions, orphan pages, and stale claims.&lt;/p&gt;&lt;p&gt;Here&apos;s a trimmed view of the &lt;code&gt;index.md&lt;/code&gt; for my &quot;Latest in AI&quot; wiki. This was built from podcast and video notes I&apos;ve taken over the past few months, and every entry links to a full page the agent writes and maintains:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Index — Latest in AI wiki&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## Sources (20)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [[nvidia-blackwell|NVIDIA Blackwell Architecture]] — Gavin Baker note on GB200/B200, token efficiency, and the shift from raw intelligence to usefulness.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [[ralph-loops|Ralph Loops in Claude Code]] — Theo note on wrapping Claude Code in external scripts for REPL-like loops.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [[ai-music-production|The Impact of AI on Music Production]] — Adam Neely/Suno note about curation vs. creation.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## Concepts (16)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [[agent-loops|Agent Loops]]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [[software-factories|Software Factories]]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [[inference-economics|Inference Economics]]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## People (8)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [[boris-cherny|Boris Cherny]] — Creator of Claude Code; cited as saying his work has shifted from prompting Claude to writing loops.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [[andrew-wilkinson|Andrew Wilkinson]] — Quoted on Claude Code helping him move at the speed of his ADHD.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I also keep a wiki for my Bible study through Lee Strobel&apos;s &lt;em&gt;The Case for Christ&lt;/em&gt;. The book is a long series of expert interviews, so the agent built out a &quot;people&quot; index with a page for every interviewee: who they are, and what they argued. I&apos;ve found this super helpful for revisiting arguments weeks later, since names tend to blend together for me without a glossary:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;## People (18)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span&gt;lee-strobel&lt;/span&gt;&lt;span&gt;]] — author/journalist; the investigator and convert.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span&gt;craig-blomberg&lt;/span&gt;&lt;span&gt;]] — New Testament scholar; gospel reliability &amp;amp; early dating.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span&gt;alexander-metherell&lt;/span&gt;&lt;span&gt;]] — physician; the medical reality of the crucifixion.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span&gt;william-lane-craig&lt;/span&gt;&lt;span&gt;]] — apologist; the empty tomb &amp;amp; the God hypothesis.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span&gt;gary-collins&lt;/span&gt;&lt;span&gt;]] — psychologist; the mental health of Jesus.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Step 4: automations&lt;/h2&gt;&lt;p&gt;These skills are useful, but I&apos;d rather not re-run these every time I go to browse my notes or ask an agent a question. Both skills have loop variants designed to run on a schedule:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/bholmesdev/llm-knowledge-base-skills/blob/main/skills/enrich-notes-loop/SKILL.md&quot;&gt;/enrich-notes-loop&lt;/a&gt;&lt;/strong&gt; wraps /enrich-note and walks every note you&apos;ve taken recently, using &lt;code&gt;enrichedAt&lt;/code&gt; to ignore previously crawled entries.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/bholmesdev/llm-knowledge-base-skills/blob/main/skills/refresh-wiki/SKILL.md&quot;&gt;/refresh-wiki&lt;/a&gt;&lt;/strong&gt; walks through your &lt;code&gt;wikis/&lt;/code&gt; directory and updates every wiki it finds. (You&apos;ll want your wikis in a &lt;code&gt;wikis/&lt;/code&gt; directory. You can tweak all of this to taste.)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;There are two options I&apos;d suggest trying: local automations to get started, and cloud automations to make the workflow durable.&lt;/p&gt;&lt;h3&gt;Local Automations with Codex&lt;/h3&gt;&lt;p&gt;You can schedule these in a few ways. One is a local automation that runs on your computer, waking up to organize the notes folder on a cron. The &lt;a href=&quot;https://openai.com/codex/&quot;&gt;Codex app&lt;/a&gt; offers this from the &lt;strong&gt;Scheduled&lt;/strong&gt; tab:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fcodex-scheduled-tab.BDEI4GZh.png&quot; alt=&quot;The Codex app sidebar with the Scheduled tab selected, showing a list of scheduled automations.&quot; width=&quot;1872&quot; height=&quot;1424&quot; /&gt;&lt;/p&gt;&lt;p&gt;From here, click &quot;Create with chat,&quot; select the directory where your notes live, and specify the skill to run. I suggest scheduling to run daily on an overnight schedule. I also suggest a lower reasoning level for /enrich-notes-loop since it&apos;s a less ambiguous skill. For wiki generation, a higher reasoning level may let the model be more thoughtful / creative. Tweak to taste.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fcodex-create-schedule.RwnG1cpb.png&quot; alt=&quot;Codex&apos;s schedule creation flow, with a chat describing the skill to run, the target directory, and a daily overnight cadence.&quot; width=&quot;1872&quot; height=&quot;1424&quot; /&gt;&lt;/p&gt;&lt;h3&gt;Cloud Automations&lt;/h3&gt;&lt;p&gt;While local automations are the easiest way to get started, they do have a pretty obvious drawback: your computer will need to be awake and running for the task to execute. Codex in particular &lt;em&gt;did&lt;/em&gt; ensure that scheduled jobs run even when a laptop lid is closed. Still, I noticed when I went out of town for a few days that my automations stopped running since I forgot to plug in my laptop... 🤦‍♂️&lt;/p&gt;&lt;p&gt;Given that, I&apos;d suggest trying cloud runners. Here&apos;s a setup guide with the tools I use:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://oz.dev/&quot;&gt;oz.dev&lt;/a&gt; to create a cloud sandbox connected to open-weight models&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://obsidian.md/sync&quot;&gt;Obsidian Sync&lt;/a&gt; to sync markdown notes to the cloud&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://obsidian.md/help/headless&quot;&gt;Obsidian&apos;s headless CLI&lt;/a&gt; to access those notes programmatically from a cloud sandbox&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;a href=&quot;https://oz.dev/&quot;&gt;Oz&lt;/a&gt; is Warp&apos;s cloud hosting platform for agents. It charges mostly at token cost, yes. But open-weight models like Kimi k2.6 are highly token efficient (3-4x fewer tokens than Claude&apos;s models) and high quality.&lt;/p&gt;&lt;p&gt;Here&apos;s the flow each scheduled run follows:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;The agent uses the Obsidian CLI to &lt;strong&gt;pull&lt;/strong&gt; the vault down into the cloud sandbox&lt;/li&gt;&lt;li&gt;It runs &lt;strong&gt;/enrich-notes-loop&lt;/strong&gt; or &lt;strong&gt;/refresh-wiki&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;It &lt;strong&gt;pushes&lt;/strong&gt; the results back up, which sync down to your machine&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I wrote two more skills to run these with the necessary cloud sandbox setup: &lt;a href=&quot;https://github.com/bholmesdev/llm-knowledge-base-skills/blob/main/skills/enrich-notes-loop-cloud/SKILL.md&quot;&gt;/enrich-notes-loop-cloud&lt;/a&gt; and &lt;a href=&quot;https://github.com/bholmesdev/llm-knowledge-base-skills/blob/main/skills/refresh-wiki-cloud/SKILL.md&quot;&gt;/refresh-wiki-cloud&lt;/a&gt;. They&apos;re thin wrappers that sync the vault down, run the base skill, and sync back up.&lt;/p&gt;&lt;p&gt;The environment and schedule setup is a handful of &lt;code&gt;oz&lt;/code&gt; CLI commands, so I encoded that as a skill too: &lt;a href=&quot;https://github.com/bholmesdev/llm-knowledge-base-skills/blob/main/skills/setup-oz-automations/SKILL.md&quot;&gt;/setup-oz-automations&lt;/a&gt;. First, make an account: go to &lt;a href=&quot;https://oz.dev/&quot;&gt;oz.dev&lt;/a&gt; and click &lt;strong&gt;&quot;start an agent.&quot;&lt;/strong&gt; That takes you through login, where you can &lt;a href=&quot;https://warp.dev/pricing&quot;&gt;create an account&lt;/a&gt;. Then install the skills (if you haven&apos;t already) and hand the rest to your agent:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; skills&lt;/span&gt;&lt;span&gt; add&lt;/span&gt;&lt;span&gt; bholmesdev/llm-knowledge-base-skills&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;Run the /setup-oz-automations skill&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;The skill checks for &lt;a href=&quot;https://github.com/warpdotdev/oz-agent-skill&quot;&gt;Warp&apos;s Oz platform skill&lt;/a&gt; (installing it with &lt;code&gt;npx skills add warpdotdev/oz-agent-skill&lt;/code&gt; if it&apos;s missing), asks for your vault name and Obsidian auth token, then creates the environment and both schedules: nightly note enrichment, weekly wiki refresh. You can pause a schedule anytime with &lt;code&gt;oz schedule pause&lt;/code&gt;, and every run leaves a session transcript you can review from the Oz dashboard.&lt;/p&gt;&lt;h2&gt;Step 5: visualizations&lt;/h2&gt;&lt;p&gt;Alright, now we have beautiful webs of markdown with tags, backlinks, index pages, and deep wikis to explore. But what if we go beyond text?&lt;/p&gt;&lt;p&gt;Since everything is markdown files in a directory, an agent can build interactive visualizations on top of them using HTML. I&apos;ve built a few examples you can plug into your setup, assuming you have your notes in a &lt;code&gt;raw/&lt;/code&gt; directory:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/bholmesdev/llm-knowledge-base-skills/blob/main/visualizations/notes-burndown.html&quot;&gt;Notes burndown&lt;/a&gt;&lt;/strong&gt;: a GitHub-style activity graph of your note-taking over time. I&apos;m using it to build a consistent journalling habit.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fnotes-burndown.BNDpjr86.png&quot; alt=&quot;A GitHub-style contribution graph of daily note-taking activity, with a tooltip listing the notes written on a given day.&quot; width=&quot;2048&quot; height=&quot;1478&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/bholmesdev/llm-knowledge-base-skills/blob/main/visualizations/thought-constellation.html&quot;&gt;Thought constellation&lt;/a&gt;&lt;/strong&gt;: display notes as a graph clustered by your most-used tags (a payoff of &lt;strong&gt;/enrich-note&lt;/strong&gt;). It helped me find my most referenced notes for a second read, and figure out gaps where I should focus next (clearly I need to read more...)&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fthought-constellation.BjYPKX9i.png&quot; alt=&quot;Notes displayed as a constellation of colored stars, clustered by tag with lines connecting related notes and a legend of topics.&quot; width=&quot;1814&quot; height=&quot;1400&quot; /&gt;&lt;/p&gt;&lt;p&gt;You can build these as one-off visualizations updated on a schedule, similar to our &lt;strong&gt;/enrich-note&lt;/strong&gt; and wiki examples. But if you want these to live-update by reading files from disk, you can view these visualizations within &lt;a href=&quot;https://hubble.md/&quot;&gt;Hubble.md&lt;/a&gt;. This is the markdown notes app I mentioned earlier. This lets you view HTML apps alongside your notes, and injects runtime APIs to read files in the current directory to build visuals that update programmatically.&lt;/p&gt;&lt;h2&gt;Go write things down!&lt;/h2&gt;&lt;p&gt;All the skills and visualizations are in &lt;a href=&quot;https://github.com/bholmesdev/llm-knowledge-base-skills&quot;&gt;this GitHub repo&lt;/a&gt;. To start, just ask your agent to convert your existing notes into Markdown (Apple Notes, Notion, Granola, etc. all support this). Then try some of the skills I shared here to see if they help you. Build a wiki around your next research project, side hobby, or agentic coding session. Invent a new visualization and share it here. I&apos;m curious what you come up with!&lt;/p&gt;&lt;p&gt;Also, give &lt;a href=&quot;https://hubble.md/&quot;&gt;Hubble.md&lt;/a&gt; a try. It&apos;s free and open-source :)&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Hubble Note</title><link>https://bholmes.dev/blog/hubble-note/</link><guid isPermaLink="true">https://bholmes.dev/blog/hubble-note/</guid><description>A notes app that helps you remember everything you wrote down.</description><pubDate>Mon, 22 Sep 2025 02:21:57 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;So... I&apos;m building a notes app. And it&apos;s called Hubble Note.&lt;/p&gt;&lt;h2&gt;What is it?&lt;/h2&gt;&lt;p&gt;Hubble Note helps you remember everything you wrote down. It combines your notes, your chats, and your web browsing into a single tool, backed by a smart little assistant called Hubble. It&apos;s as simple as Apple Notes, but as powerful as Notion or Obsidian, without the learning curve.&lt;/p&gt;&lt;h2&gt;Why Hubble?&lt;/h2&gt;&lt;p&gt;Apple Notes is awesome. You open it up, write something down, and close it when you’re done. It’s the default writing tool for pretty much everyone because of how dead simple it is.&lt;/p&gt;&lt;p&gt;And yet... you can’t find anything you wrote down. It’s a junk drawer where everything useful gets thrown and forgotten forever.&lt;/p&gt;&lt;p&gt;Except, unlike a junk drawer, some of your most important and valuable things get thrown in there. That Thai curry recipe your friend sent you a year ago that would be &lt;em&gt;perfect&lt;/em&gt; for the house party this weekend. That incredible YouTube lecture you pasted in your notes that would &lt;em&gt;really&lt;/em&gt; help for this job interview coming up. Those years of weeknight writing sessions you’d &lt;em&gt;love&lt;/em&gt; to catalog into one big list. But if you want to do any of these things, you’re handed tools straight out of 1995: a dumb search engine, and a chronological history. GLHF.&lt;/p&gt;&lt;p&gt;Why can’t there be an Apple Notes wizard to clean everything up for you? A wizard to come up with the right search term for anything you ask? A wizard to organize your thoughts into neat little folders? A wizard to condense a web of research rabbit holes into a clean abstract with backlinks?&lt;/p&gt;&lt;p&gt;I tried to be that wizard once. I followed the Zettelkasten method, and spent months perfecting Notion and Obsidian templates to find the perfect workflow. And after years of trying… I went right back to Apple Notes like the rest of you. Nothing beats a simple scratchpad you can open, write something down, and close when you’re done.&lt;/p&gt;&lt;p&gt;So why can’t we have something as simple as Apple Notes, but as powerful at organizing and expanding your thoughts as something like Notion or Obsidian? All without the learning curve and upkeep those tools demand?&lt;/p&gt;&lt;p&gt;That’s what Hubble Note is here to do. Hubble Note is a notes app that’s as simple to use as Apple Notes. But when it’s time to find, organize, or research ideas further, you can talk to Hubble to do what you need. Call it… Claude Code for your notes. Except without the code, and maybe without the Claude if GPT-5 proves smarter.&lt;/p&gt;&lt;p&gt;[this is where a slick prototype will go. It’s coming, I swear]&lt;/p&gt;&lt;h2&gt;Why me?&lt;/h2&gt;&lt;p&gt;I have… an unhealthy obsession with writing things down. Both as a content creator and as a Markdown-loving engineer.&lt;/p&gt;&lt;p&gt;I think a timeline will help:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;2018:&lt;/strong&gt; Started a technical blog to talk about web technologies (&lt;a href=&quot;https://dev.to/bholmesdev&quot;&gt;hey dev.to&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;2019:&lt;/strong&gt; Built a self-hosted blog using web technologies to talk about web technologies (&lt;a href=&quot;https://bholmes.dev/&quot;&gt;hey bholmes.dev&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;2021:&lt;/strong&gt; Rebuilt that website without a framework, and accidentally invented my own. It was based on Vite and… yeah, Markdown (&lt;a href=&quot;https://slinkity.dev/&quot;&gt;hey Slinkity&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;2022:&lt;/strong&gt; Got hired to work on a new framework people &lt;em&gt;actually&lt;/em&gt; used, and made it easier to build, organize, and extend your Markdown-based content (&lt;a href=&quot;https://astro.build/&quot;&gt;hey Astro&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;2024:&lt;/strong&gt; Started working with LLMs, and realized they’re really good at writing. Like, better than me. And like me, they love Markdown too (hey GPT and Claude et al.)&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Present:&lt;/strong&gt; Decided it’s time to build a notes app that combines everything I’ve learned into one tool: a Markdown-backed writing app with a lovable agent that helps you build, organize, and extend your thoughts (hey Hubble)&lt;/p&gt;&lt;h2&gt;When can I try it?&lt;/h2&gt;&lt;p&gt;Hubble is an experimental side project, so I won’t make any promises on release dates. But to watch the sausage get made, and hear when the sausage &lt;em&gt;gets&lt;/em&gt; made, follow my &lt;a href=&quot;https://x.com/bholmesdev&quot;&gt;Twitter&lt;/a&gt; and &lt;a href=&quot;https://youtube.com/@bholmesdev&quot;&gt;YouTube&lt;/a&gt; for updates, like this:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;🔥 NEW DATABASE JUST DROPPED 🔥&lt;br /&gt;It&apos;s this thing called a &quot;file system&quot; apparently. Cross-platform, local first, zero dependencies. Idk if it&apos;ll scale though &lt;a href=&quot;https://t.co/iLHe4sNhXq&quot;&gt;pic.twitter.com/iLHe4sNhXq&lt;/a&gt;&lt;/p&gt;— Ben Holmes (@BHolmesDev) &lt;a href=&quot;https://x.com/BHolmesDev/status/1964778237722300875?ref_src=twsrc%5Etfw&quot;&gt;September 7, 2025&lt;/a&gt;&lt;/blockquote&gt;

 &lt;p&gt;Excited to have you on this journey!&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Response to DHH on the gift of open source</title><link>https://bholmes.dev/blog/open-source-gift-exchange/</link><guid isPermaLink="true">https://bholmes.dev/blog/open-source-gift-exchange/</guid><description>Open source is like a gift exchange. DHH thinks these gifts should be crafted out of self interest, but I disagree. Here&apos;s why.</description><pubDate>Sun, 20 Jul 2025 21:13:41 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;I listened to the &lt;a href=&quot;https://www.youtube.com/watch?v=vagyIcmIGOQ&quot;&gt;DHH x Lex Fridman podcast&lt;/a&gt; this past week. It’s 6 hours long, and genuinely I wish it were 10+. There are &lt;em&gt;phenomenal&lt;/em&gt; lessons in here about the beauty of programming language design, the power of open source, the importance of family… just go listen to it.&lt;/p&gt;&lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/vagyIcmIGOQ&quot; width=&quot;560&quot; height=&quot;315&quot; allowfullscreen&gt;  &lt;/iframe&gt;  &lt;p&gt;I was originally going to write a TLDR about everything I learned. But there was one part of the podcast that I wanted to focus on: &lt;a href=&quot;https://youtu.be/vagyIcmIGOQ?si=POt9h7oZ2RuvdTv6&amp;amp;t=20196&quot;&gt;DHH’s comparison of open source to gift giving&lt;/a&gt;. It speaks to a lot of my past work as an open source maintainer at Astro, and DHH takes a stance that I… &lt;em&gt;almost&lt;/em&gt; agree with. But also kind of don’t.&lt;/p&gt;&lt;h2&gt;Open source is a gift exchange&lt;/h2&gt;&lt;p&gt;Here’s a direct quote to start:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;In open source, the customer is a &lt;strong&gt;receiver of gifts&lt;/strong&gt;. We are having a gift exchange. I show up and give you my code. If you like it, you can use it. And if you have some code that fits in with where I’m going with this, I would love to get those gifts back. And we can keep trading like that… Together, we pull all the gifts such that someone showing up that’s brand new gets a mountain of gifts.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I love this analogy. Sharing Ruby on Rails with the world was an extraordinary gift that changed the course of web development for decades to come. What’s more, that gift wasn’t a one-sided act; it became a two-sided &lt;em&gt;exchange.&lt;/em&gt; One person (DHH) shared an idea with the world, it solved real problems for like-minded people, and they shared their own libraries and contributions in return. No money was exchanged, but ideas and gratitude were.&lt;/p&gt;&lt;p&gt;That said, let’s hear the next line:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;This is the magic thing of open source. It increases the total sum value of what’s in the commons when we all pursue &lt;strong&gt;our own self-interest.&lt;/strong&gt;”&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Increases the value of what’s in the commons… when we pursue our &lt;em&gt;own&lt;/em&gt; self-interest.&lt;/p&gt;&lt;p&gt;Why self interest?&lt;/p&gt;&lt;p&gt;I agree with this idea on the surface; since open source projects are “gifts,” not paid services, you, the maintainer, should follow the path that &lt;em&gt;you&lt;/em&gt; set for that project. A clear path keeps you in the game without causing burnout. It also keeps the direction of the project certain without a “designed by committee” roadmap. And crucially, it puts up boundaries around the open source relationship. I give this gift to you… but it’s ultimately built for &lt;em&gt;my&lt;/em&gt; self interest. I will not be swayed by &lt;em&gt;your&lt;/em&gt; self interest. Don’t like what I’m doing? Well I don’t care! Fork it and build what you wish. This is what you might call a “benevolent dictatorship,” which is common across the largest open source projects out there.&lt;/p&gt;&lt;p&gt;There’s nothing inherently wrong with that level of detachment. It may be a healthy line to draw when you’re opening yourself up to community scrutiny. Still, I don’t think that’s what &lt;em&gt;gift giving&lt;/em&gt; truly means.&lt;/p&gt;&lt;h2&gt;What gift giving means&lt;/h2&gt;&lt;p&gt;There was a &lt;a href=&quot;https://www.youtube.com/watch?v=wLb9g_8r-mE&amp;amp;t=984s&quot;&gt;recent interview between Jony Ive and Stripe CEO Patrick Collison&lt;/a&gt; about the importance of designing for other people.&lt;/p&gt;&lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/wLb9g_8r-mE&quot; width=&quot;560&quot; height=&quot;315&quot; allowfullscreen&gt;  &lt;/iframe&gt;  &lt;p&gt;Jony Ive makes this point on &lt;a href=&quot;https://youtu.be/wLb9g_8r-mE?si=_5nterVSNf4EUX5f&amp;amp;t=1877&quot;&gt;designing for other members of your team&lt;/a&gt;:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Make things for other people. If you can put that in your daily routine, making things for others on your team, that really puts you in a lovely place.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;In Jony Ive’s view, designing for someone else is a very vulnerable and necessary practice. You’re forging a relationship by building with someone else in mind. You’re also opening yourself to criticism, which can be scary.&lt;/p&gt;&lt;p&gt;But there’s a reason that the vulnerability is worth pursuing: &lt;strong&gt;gratitude&lt;/strong&gt;. When you build something for someone else, that someone will be grateful for the time and effort you put into it.&lt;/p&gt;&lt;p&gt;Vulnerability is rewarded by gratitude.&lt;/p&gt;&lt;p&gt;Building relationships this way is a very human and “lovely” practice. And it’s what I’d call… &lt;strong&gt;good gift giving.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Going back to DHH’s view on open source, there’s a lot less sacrifice involved. Ruby on Rails is a gift to the community, but it’s still &lt;em&gt;for&lt;/em&gt; Basecamp. It’s designed for the needs of &lt;em&gt;their&lt;/em&gt; company, and not necessarily yours. If Ruby on Rails happens to map onto the needs of your company, great! You can enjoy the open source gifts that they’ve shared. But if it doesn’t map onto your needs… well, there’s no guarantee that they’ll ever build with your needs in mind.&lt;/p&gt;&lt;p&gt;Now don’t get me wrong: Ruby on Rails &lt;em&gt;is&lt;/em&gt; a gift. It shows a lot of vulnerability to share a framework for building websites, and the community was certainly grateful for DHH&apos;s contribution. But if the roadmap to follow is driven by self-interest alone, he’s not selflessly designing for other people as Jony Ive might suggest. It’s gift giving, but a kind of… &lt;em&gt;selfish&lt;/em&gt; gift giving.&lt;/p&gt;&lt;h2&gt;The consequence of selfish gift giving&lt;/h2&gt;&lt;p&gt;Now, a disclaimer: I’m not a member of the Ruby on Rails community. I don’t know how the community has influenced the Ruby on Rails roadmap. I only have DHH’s stance on this podcast and a few spare tweets to go off of. Still, I saw an example of how selfish gift giving can rub people the wrong way: TypeScript support in Stimulus.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stimulus.hotwired.dev/&quot;&gt;Stimulus&lt;/a&gt; is an open source library from Basecamp that makes working with web components simpler. It’s the backbone of interactivity across Basecamp’s products, with &lt;a href=&quot;https://www.hey.com/&quot;&gt;Hey.com&lt;/a&gt; being a recent example. Naturally, those outside of Basecamp wanted to adopt Stimulus as well. The problem is, some of these interested users didn’t share DHH’s ideals about how software should be written: The ideals of raw text editing without completions, or of using JavaScript without types. So, despite DHH’s preferences, they’d prefer to have TypeScript support built into the library.&lt;/p&gt;&lt;p&gt;And TypeScript support &lt;em&gt;was&lt;/em&gt; a feature of Stimulus for some time. Community members decided to be vulnerable with their views and contribute this gift TypeScript support to the project.&lt;/p&gt;&lt;p&gt;Did they receive gratitude in return? Well, with the next major release… DHH decided to remove type completions entirely. The community was understandably upset. In the eyes of DHH, TypeScript completions didn’t match how he wanted software to be developed. He also didn’t want to maintain something he didn’t like. So, out they went. Gift rejected.&lt;/p&gt;&lt;p&gt;Was DHH wrong to make this call? It’s honestly not my place to say. Maybe maintaining TypeScript types was leading to burnout on Stimulus, so it needed to be removed for DHH to continue stewarding the project. Or maybe his wider narrative on how software should be written is a core principle of the project worth sticking to. If some community members disagree, they aren’t worth serving. Or better yet, he can change their mind.&lt;/p&gt;&lt;p&gt;On the other hand, maybe removing types was a selfish choice that DHH didn’t need to make. Using Jony Ive’s design philosophy, TypeScript types were a design decision suggested by, and built for, other people. It comes with a level of vulnerability (I built these completions for you, hope you like them), and the expected payoff is gratitude from the library’s community and maintainers. But DHH wasn’t interested in the gratitude of the TypeScript community, and didn’t want to build a relationship with the TypeScript community. So the gift exchange ended there.&lt;/p&gt;&lt;h2&gt;My time at Astro&lt;/h2&gt;&lt;p&gt;For the past few years, I’ve been a core maintainer of &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;, an open source framework for building content-driven websites. From the beginning, this project was built by a core &lt;em&gt;team&lt;/em&gt; (not a benevolent dictator), attracting a larger and larger community over time. Today, Astro sits at nearly 935 contributors to the &lt;a href=&quot;https://github.com/withastro/astro&quot;&gt;core repo&lt;/a&gt;, and &lt;em&gt;over a thousand&lt;/em&gt; contributors to &lt;a href=&quot;https://github.com/withastro/docs&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;There are a number of reasons the Astro community grew to that size. For one, there was the openness of our docs team. Our docs leaders set clear guidelines for what good documentation looked like, and made the contribution process as accessible as possible. Just look at &lt;a href=&quot;https://www.rainsberger.ca/blog/community-driven-astro-docs/&quot;&gt;this post Sarah wrote back in June 2022&lt;/a&gt;. A community member asked “have you written anything about your community-driven approach to docs at Astro?” And she wrote an essay on the subject. Never change Sarah :)&lt;/p&gt;&lt;p&gt;After setting these expectations, the core team stepped back, and let the community step in. Internationalization is my favorite example, since translations are notoriously hard for open source projects to get right (or get at all). I18n contributions were slow at first. But overtime, contributors started translating our docs to Spanish, then French, then Japanese… until we hit a serious tipping point. The Astro docs sit at 14 supported languages today.&lt;/p&gt;&lt;p&gt;How did we get so many translations? The answer’s the same as any good gift exchange: reward vulnerability with gratitude. If a community member translates a single page of our docs into their language, we don’t just say &quot;LGTM&quot; and merge the PR. We give them a special badge in our discord community. We give a thorough PR review with kind, thorough feedback about what to improve for next time. Multiple maintainers stop by the PR to say thanks, even if that PR has already been merged. I’ll say it again: reward vulnerability with gratitude.&lt;/p&gt;&lt;p&gt;This principle is the backbone of Astro core as well. We have a core team that guides the direction of the project, yes. But we &lt;em&gt;also&lt;/em&gt; have a public roadmap, and a public discussion board for anyone to share ideas. The system isn’t perfect (we’re working on ways to make roadmap contribution more accessible), but it’s led to many features landing in Astro that the core team wouldn’t have thought of. &lt;a href=&quot;https://github.com/withastro/roadmap/pull/963&quot;&gt;Server islands&lt;/a&gt;, &lt;a href=&quot;https://github.com/withastro/roadmap/discussions/228#discussioncomment-11545091&quot;&gt;Vercel ISR&lt;/a&gt;, and &lt;a href=&quot;https://github.com/withastro/roadmap/pull/1039&quot;&gt;first-party font support&lt;/a&gt; to name a few.&lt;/p&gt;&lt;p&gt;We easily could’ve said “we don’t want the hassle of managing Spanish translations,” or “sorry, we don’t believe in serverless hosting.” But we decided to let the community shape the roadmap a bit more. The community showed us what was possible with the gifts they gave.&lt;/p&gt;&lt;p&gt;Of course, there&apos;s a balance between accepting and rejecting gifts. We draw boundaries between what should exist in core vs. what should be maintained by the community (ex. adapters for exotic deployment hosts), with an integration system to make the framework extensible. We also formalized which i18n translations our docs would and would not support, &lt;a href=&quot;https://astro.build/blog/astro-i18n/&quot;&gt;with a blog post&lt;/a&gt; describing our process. In all cases though, there&apos;s a common theme: &lt;strong&gt;clear communication.&lt;/strong&gt; If we&apos;re going to reject gifts from a certain community, they deserve to know why. They also deserve an &lt;a href=&quot;https://astro.build/chat&quot;&gt;accessible discussion forum&lt;/a&gt; to voice their opinions.&lt;/p&gt;&lt;p&gt;I’ll make one more contrast between Astro and Ruby on Rails: &lt;strong&gt;Astro doesn’t belong to any company.&lt;/strong&gt; Astro isn’t built for a Basecamp, it’s built for Astro’s own sake. Instead of designing for a single party, we design for a broad community. We design for other people.&lt;/p&gt;&lt;p&gt;Sure, Astro could be a much more focused project if we partnered with a company and built for their needs. But I imagine the community would be much smaller, and community sentiment would be more mixed.&lt;/p&gt;&lt;h2&gt;Conclusions&lt;/h2&gt;&lt;p&gt;I guess I’ll leave off with this: yes, free and open source software is a gift exchange. Maintainers are sacrificing their time and their brainpower without asking for a cent in return. And for this gift exchange to continue, and to grow, we need to keep rewarding vulnerability with gratitude.&lt;/p&gt;&lt;p&gt;You may prefer to keep your community small and only exchange gifts with like-minded individuals. Reject the TypeScript heads, find the text editor junkies. Or you may want to attract a broader community, and try to design for people that &lt;em&gt;aren’t&lt;/em&gt; necessarily like you. It’s harder to do, but in my opinion, very rewarding.&lt;/p&gt;&lt;p&gt;So, contributors: keep being vulnerable. And maintainers: keep showing gratitude. Open source is a powerful thing, so let’s make it flourish together.&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>The python environment setup cheatsheet</title><link>https://bholmes.dev/blog/simple-python-env-setup/</link><guid isPermaLink="true">https://bholmes.dev/blog/simple-python-env-setup/</guid><description>A checklist for setting up your Python 3 environment in any editor</description><pubDate>Sun, 01 Sep 2024 22:02:02 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;Python seems to have a million different options to set up an environment. So I did what any good developer does: I asked Claude. Here&apos;s the checklist I used.&lt;/p&gt;&lt;h2&gt;Setup&lt;/h2&gt;&lt;p&gt;Prerequisite: you&apos;ll want to have &lt;a href=&quot;https://www.python.org/downloads/&quot;&gt;&lt;code&gt;python3&lt;/code&gt; installed on your machine&lt;/a&gt;.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Create a virtual environment in your current directory with &lt;code&gt;python3 -m venv .venv&lt;/code&gt;. &lt;code&gt;.venv/&lt;/code&gt; will be a generated folder, so add this to your &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Activate the environment with &lt;code&gt;source .venv/bin/activate&lt;/code&gt;. Run this every time you open your project directory from your editor, or when you open a new terminal instance.&lt;/li&gt;&lt;li&gt;List all dependencies in a &lt;code&gt;requirements.txt&lt;/code&gt; file.&lt;/li&gt;&lt;li&gt;Install dependencies with &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;. Run this again whenever you add a new dependency to your &lt;code&gt;requirements.txt&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Run your scripts with &lt;code&gt;python3 name.py&lt;/code&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;Editor tooling&lt;/h2&gt;&lt;p&gt;VS Code seems to &lt;a href=&quot;https://code.visualstudio.com/docs/python/environments#_create-a-virtual-environment-in-the-terminal&quot;&gt;generate helpful prompts&lt;/a&gt; when creating a new environment to set up editor tooling.&lt;/p&gt;&lt;p&gt;Outside of VS Code, I would install the pyright LSP. You can &lt;a href=&quot;https://github.com/fannheyward/coc-pyright&quot;&gt;install this into neovim environments&lt;/a&gt;, or use the built-in pyright LSP when using Zed (my editor of choice).&lt;/p&gt;&lt;p&gt;To tell pyright where your python env is located, add a &lt;code&gt;pyrightconfig.json&lt;/code&gt; with a path to your project directory (&lt;code&gt;.&lt;/code&gt; for the current directory) and the name of your venv folder (&lt;code&gt;.venv&lt;/code&gt;):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;venvPath&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;.&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;venv&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;.venv&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/article&gt;</content:encoded></item><item><title>Building serverless applications with Fauna + GraphQL</title><link>https://bholmes.dev/blog/serverless-apps-fauna-gql-astro/</link><guid isPermaLink="true">https://bholmes.dev/blog/serverless-apps-fauna-gql-astro/</guid><description>Let&apos;s learn how &quot;serverless&quot; and &quot;GraphQL&quot; compliment each other when building frontend-focused web apps.</description><pubDate>Thu, 24 Mar 2022 22:48:29 GMT</pubDate><content:encoded>&lt;article&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;This article is sponsored by Fauna.&lt;/strong&gt; If you are interested in producing similar content, apply to &lt;a href=&quot;https://www2.fauna.com/write-with-fauna&quot;&gt;Write with Fauna&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Serverless and GraphQL have become pretty prolific buzzwords in the backend space. They each serve (heh) fairly different needs; the former takes the infrastructure management out of the backend, while the latter handles the efficiency and documentation pieces.&lt;/p&gt;&lt;p&gt;So, how do these approaches compliment each other? Let’s explore:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;🤷 why you’d try serverless and GraphQL&lt;/li&gt;&lt;li&gt;🤝 the intersection of needs they both address&lt;/li&gt;&lt;li&gt;🔨 building a GraphQL backend using Netlify serverless&lt;/li&gt;&lt;li&gt;🗄 connecting to a persistent database using Fauna&lt;/li&gt;&lt;li&gt;🚀 deploying a live static website using Astro&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Key terms&lt;/h2&gt;&lt;p&gt;Before diving in, let’s define some import terms we’ll explore throughout the walkthrough.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Serverless&lt;/strong&gt; - a method of deploying backend logic to isolated functions executed and managed on a pay-as-you-go model&lt;/li&gt;&lt;li&gt;&lt;strong&gt;GraphQL&lt;/strong&gt; - a protocol for requesting information via HTTP, comparable to REST&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Jamstack&lt;/strong&gt; - building web apps with a decoupled architecture. Your website’s frontend handles its own routing and view logic (often as an &lt;a href=&quot;https://bholmes.dev/blog/spas-clientside-routing/&quot;&gt;SPA&lt;/a&gt;), and sends requests to APIs as-needed.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Why serverless?&lt;/h2&gt;&lt;p&gt;Have you seen those pay-as-you-play games gaining traction over the past few years? Let’s take Fortnite as an example. It’s 100% free to download and play &lt;em&gt;for life,&lt;/em&gt; with optional paid extras for enthusiasts that want to get more from the game. This is perfect for people planning an impromptu game night for instance. If your buddy doesn’t have the game installed, no sweat! Just download the launcher and you’re battle royale-ing in no time.&lt;/p&gt;&lt;p&gt;This is a pretty good analogy for serverless in my opinion. Instead of building, deploying, and managing a routed server of your own (like a traditional video game charging $X up-front), you’re free to start with a single serverside function that someone else maintains. No need to pay for a complete Linux server instance yourself. Instead, write a single function in a (supported) programming language that receives a user request, computes the response necessary, and deploys to an &lt;em&gt;existing&lt;/em&gt; server infrastructure. For here, you’re simply charged by the number of requests each functions fulfills. Free to enter, pay at scale.&lt;/p&gt;&lt;p&gt;This is perfect for “game night,” when your team needs to rapidly prototype a new project and get it deployed both quickly and cheaply. It should also save your team from managing the infrastructure &lt;em&gt;surrounding&lt;/em&gt; each of those one-off serverless functions you write. Sure, you’ll be at the whim of &lt;a href=&quot;https://aws.amazon.com/serverless/&quot;&gt;AWS&lt;/a&gt;, &lt;a href=&quot;https://azure.microsoft.com/en-us/solutions/serverless/&quot;&gt;Azure&lt;/a&gt;, &lt;a href=&quot;https://cloud.google.com/serverless&quot;&gt;Google Cloud&lt;/a&gt;, and more, but that &lt;em&gt;might&lt;/em&gt; be more reliable than a personal IT department.&lt;/p&gt;&lt;h2&gt;Why GraphQL?&lt;/h2&gt;&lt;p&gt;To best understand GraphQL, let’s start with a food-based analogy for REST. I personally think of REST like ordering from an upscale, sit-down restaurant. Hungry for the steak dinner? Make a request to your server (heh, see what I did there) and get back a gourmet response 🥩&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;GET&lt;/span&gt;&lt;span&gt; /kitchen/steak-dinner&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; 200&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	steak:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		cooked:&lt;/span&gt;&lt;span&gt; &apos;medium-rare&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		size:&lt;/span&gt;&lt;span&gt; &apos;xl&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	spinachSalad:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		prepared:&lt;/span&gt;&lt;span&gt; &apos;steamed&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		toppings:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&apos;tomato&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;pepper&apos;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	chimichurri:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		numGarlicCloves:&lt;/span&gt;&lt;span&gt; 2,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		cupsParsley:&lt;/span&gt;&lt;span&gt; 1.5,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This response is hand-tailored by the chef for the perfect meal. Still, some people might &lt;em&gt;not&lt;/em&gt; need all these bells and whistles. What if you don’t need the chimichurri sauce? Or you aren’t hungry for spinach salad? Or you don’t care how the steak is cooked? If you’re at an upscale restaurant where the chef always knows best... good luck changing that prebuilt plate.&lt;/p&gt;&lt;h3&gt;How GraphQL turns menus into buffets&lt;/h3&gt;&lt;p&gt;GraphQL turns this experience into a buffet model. Instead of a single option for everyone, GraphQL gives &lt;em&gt;you,&lt;/em&gt; the user, the power to choose what you want.&lt;/p&gt;&lt;p&gt;Say we want that steak from earlier with no extra sides, and we don’t care how it’s cooked. We can pass these options on the request body like so:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt; /kitchen&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;body:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	steak&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		size&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And kitchen can take this request back to the &lt;code&gt;/kitchen&lt;/code&gt;, responding with your tailor-made meal:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; 200&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		steak:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			size:&lt;/span&gt;&lt;span&gt; &apos;xl&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There’s a few important distinctions to make from our REST example:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;You’re now making a POST request instead of a GET.&lt;/strong&gt; This is because &lt;em&gt;you&lt;/em&gt; &lt;em&gt;now have an opinion on what gets served.&lt;/em&gt; To “post” that request to the kitchen, you need to pass a body with your request.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;You post generically to the &lt;code&gt;/kitchen&lt;/code&gt;,&lt;/strong&gt; instead of requesting a specific item like &lt;code&gt;/kitchen/steak-dinner&lt;/code&gt;. This stems from the inverse of control we discussed in #1. In GraphQL, all requests are passed to a single handler (the kitchen) that receives and “resolves” incoming requests.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;This approach is similar to a buffet. There’s one row of options available for you to inspect, and you’re free to choose the exact options you prefer. GraphQL &lt;a href=&quot;https://www.youtube.com/watch?v=CHNAnGSmQeA&quot;&gt;even offers a “playground”&lt;/a&gt; to explore all available options from a given API. Like a buffet, you’re free to browse this self-documented schema of options to help craft a well-formatted request.&lt;/p&gt;&lt;p&gt;To summarize these benefits:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;GraphQL brings power back to the client.&lt;/strong&gt; Instead of requesting needlessly large payloads from prebuilt REST endpoints, you can choose the exact information a given frontend view needs.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;GraphQL is self-documenting.&lt;/strong&gt; Like a buffet, you can browse your API’s schema so there aren’t any surprises on that response body. This even helps with &lt;a href=&quot;https://www.graphql-code-generator.com/&quot;&gt;type generation in your codebase&lt;/a&gt; when you choose TypeScript 👀&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Why serverless + GraphQL?&lt;/h2&gt;&lt;p&gt;We’ve discussed the selling point for each of these. So what makes the combination of &lt;em&gt;both&lt;/em&gt; so special?&lt;/p&gt;&lt;p&gt;Well, there’s a cross-section of needs that each of these help to address: &lt;strong&gt;teams want to ship experiences fast without asking questions.&lt;/strong&gt; I personally identify with this as a &lt;em&gt;mostly&lt;/em&gt; frontend developer. I don’t want to host, document, and maintain a server of my own; I just want to build efficient frontends with pits of success to save me as I scale. Here’s how serverless and GraphQL can help your team get there:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;By hosting GQL resolvers on serverless, you’re free to start for free and scale cheaply. Platforms like &lt;a href=&quot;https://www.netlify.com/products/functions/&quot;&gt;Netlify&lt;/a&gt; and &lt;a href=&quot;https://vercel.com/docs/concepts/functions/serverless-functions&quot;&gt;Vercel&lt;/a&gt; also offer incredible developer experience to deploy these functions alongside your existing frontends (if you build using web tech).&lt;/li&gt;&lt;li&gt;With a GQL API in place, you’re given a self-documenting “buffet” endpoint to hand-pick the right data for views in your application. This saves you from managing separate routes for each REST endpoint, and stitching together documentation as your backend scales.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Both of these offer a bias towards frontend-leaning teams planning to work fast.&lt;/p&gt;&lt;h3&gt;When is serverless + GraphQL a &lt;em&gt;bad&lt;/em&gt; choice?&lt;/h3&gt;&lt;p&gt;To be honest, there isn’t a strong reason this &lt;em&gt;combination&lt;/em&gt; in particular would be worrisome. It truthfully comes down to flaws with each technology in isolation.&lt;/p&gt;&lt;h3&gt;Flaws with serverless&lt;/h3&gt;&lt;p&gt;Serverless may be a poor choice for larger teams that plan to manage their own infrastructure. At scale, it might be preferable (and even cheaper) to purchase an AWS box to host your application and manage that server yourself.&lt;/p&gt;&lt;p&gt;There’s also &lt;a href=&quot;https://www.serverless.com/blog/keep-your-lambdas-warm/&quot;&gt;the “cold start problem”&lt;/a&gt; to consider when using serverless. Since each function is automatically spun up and spun down on request, some requests &lt;em&gt;may&lt;/em&gt; be slower as your hosting provider manages your function’s lifecycle.&lt;/p&gt;&lt;h3&gt;Flaws with GraphQL&lt;/h3&gt;&lt;p&gt;GraphQL may be a poor choice for simpler APIs, or for teams completely new to the technology. Now don’t get me wrong; GraphQL is preferable from the frontend layer &lt;em&gt;most&lt;/em&gt; of the time due to its granular control and self-documentation. The real struggle emerges when implementing the backend behind this magical request buffet.&lt;/p&gt;&lt;p&gt;If you like batteries-included options, popular database services like &lt;a href=&quot;https://docs.fauna.com/fauna/current/learn/quick_start/gql_quick_start?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=Ben_Holmes_Serverless&quot;&gt;Fauna&lt;/a&gt; and content management services (CMSs) like &lt;a href=&quot;https://www.contentful.com/developers/docs/references/graphql/&quot;&gt;Contentful&lt;/a&gt; each generate GraphQL backend automatically. But when you’re implementing these resolvers yourself, managing a well-crafted API on a single endpoint &lt;em&gt;can&lt;/em&gt; get complex. I recommend exploring &lt;a href=&quot;https://www.apollographql.com/docs/apollo-server/v2/getting-started/&quot;&gt;Apollo’s getting started guide&lt;/a&gt; to decide whether this learning curve is right for your team.&lt;/p&gt;&lt;h2&gt;Let’s build a simple app with GraphQL, Netlify serverless, Astro, and FaunaDB&lt;/h2&gt;&lt;p&gt;It’s best to get our hands dirty discussing new tools. As an example, let’s build a small Jamstack website to display photos of far-out galaxies, courtesy of the &lt;a href=&quot;https://api.nasa.gov/&quot;&gt;NASA API&lt;/a&gt; 🌌&lt;/p&gt;&lt;p&gt;Here’s a brief overview of the tech we’re using:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt; to spin up and host our serverless backend&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/apollographql/apollo-server/tree/main/packages/apollo-server-lambda&quot;&gt;Apollo&lt;/a&gt; to generate a small GraphQL endpoint&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://fauna.com/?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=Ben_Holmes_Serverless&quot;&gt;Fauna&lt;/a&gt; to persist our galaxies in a database&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; to build a static site that surfaces this information&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Clone the starter code on your machine&lt;/h3&gt;&lt;p&gt;Before jumping into the code snippets, &lt;strong&gt;I recommend &lt;a href=&quot;https://github.com/ben-holmes-blog-demos/faunadb-graphql-serverless-demos/tree/main-getting-started&quot;&gt;cloning the “getting started” branch on this repository&lt;/a&gt;.&lt;/strong&gt; It’ll offer a nice base for our frontend application, and set up some config files for linting and environment variables.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/ben-holmes-blog-demos/faunadb-graphql-serverless-demos/tree/main-getting-started&quot;&gt;Clone the repository here →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;h3&gt;Overview of the project&lt;/h3&gt;&lt;p&gt;Let’s break down our starter project’s structure:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;📂&lt;/span&gt;&lt;span&gt; netlify/functions/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	nasa.mjs&lt;/span&gt;&lt;span&gt; # our serverless endpoint&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;📂&lt;/span&gt;&lt;span&gt; utils/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	getFaunaClient.js&lt;/span&gt;&lt;span&gt; # helper for our Fauna DB connection&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	mockGalaxies.js&lt;/span&gt;&lt;span&gt; # static dummy data to get started&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	seed.js&lt;/span&gt;&lt;span&gt; # small script to throw this dummy data on the DB&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;📂&lt;/span&gt;&lt;span&gt; www/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	📂&lt;/span&gt;&lt;span&gt; src/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		📂&lt;/span&gt;&lt;span&gt; layouts/&lt;/span&gt;&lt;span&gt; # template shared across our website&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		📂&lt;/span&gt;&lt;span&gt; pages/&lt;/span&gt;&lt;span&gt; # individual routes on our website&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	📂&lt;/span&gt;&lt;span&gt; styles/&lt;/span&gt;&lt;span&gt; # styles for space aesthetic 👩‍🚀&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.env.development&lt;/span&gt;&lt;span&gt; # env variable to store our API route&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;package.json&lt;/span&gt;&lt;span&gt; # all our CLI scripts and dependencies&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# other miscellaneous config files...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Most of our frontend has been pre-built so we can focus on GraphQL + serverless. We’ll jump into &lt;code&gt;netlify/functions&lt;/code&gt; and those &lt;code&gt;utils/&lt;/code&gt; to get to the action!&lt;/p&gt;&lt;h2&gt;Spin up your function locally with the Netlify CLI&lt;/h2&gt;&lt;p&gt;You’ll notice some starter code under &lt;code&gt;netlify/functions/nasa.mjs&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; handler&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    statusCode: &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    body: &lt;/span&gt;&lt;span&gt;&apos;hello galaxy!&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you might imagine, we should get the response “hello galaxy!” whenever we call this endpoint.&lt;/p&gt;&lt;p&gt;Let’s try querying this function locally. First, &lt;a href=&quot;https://nodejs.org/en/download/&quot;&gt;ensure you have node + npm installed on your machine.&lt;/a&gt; Now you can install &lt;a href=&quot;https://docs.netlify.com/cli/get-started/&quot;&gt;the Netlify CLI&lt;/a&gt; globally like so:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; netlify-cli&lt;/span&gt;&lt;span&gt; -g&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;netlify&lt;/span&gt;&lt;span&gt; --version&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you get a &lt;code&gt;--version&lt;/code&gt; output in your terminal, you should be good to go! Note: &lt;strong&gt;you might need to close and restart your terminal&lt;/strong&gt; if you run into issues with this check.&lt;/p&gt;&lt;p&gt;You’ll also need a Netlify account for simpler debugging and deployment in a moment. Go ahead and &lt;a href=&quot;https://www.netlify.com/&quot;&gt;make a free Netlify account&lt;/a&gt; here.&lt;/p&gt;&lt;p&gt;Next, authenticate from your terminal and follow the prompts:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;netlify&lt;/span&gt;&lt;span&gt; login&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If all goes well, you’re ready to spin up a local Netlify server:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; # install project dependencies first&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt; ntl:dev&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;☝️ If you check the &lt;code&gt;package.json&lt;/code&gt; at the base of this project, you’ll see that &lt;code&gt;ntl:dev&lt;/code&gt; maps to the &lt;code&gt;netlify dev&lt;/code&gt; command. You’ll also see a whole host of scripts we’ll try in the coming sections!&lt;/p&gt;&lt;p&gt;You should see a console output like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;◈&lt;/span&gt;&lt;span&gt; Netlify&lt;/span&gt;&lt;span&gt; Dev&lt;/span&gt;&lt;span&gt; ◈&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;◈&lt;/span&gt;&lt;span&gt; No&lt;/span&gt;&lt;span&gt; app&lt;/span&gt;&lt;span&gt; server&lt;/span&gt;&lt;span&gt; detected.&lt;/span&gt;&lt;span&gt; Using&lt;/span&gt;&lt;span&gt; simple&lt;/span&gt;&lt;span&gt; static&lt;/span&gt;&lt;span&gt; server&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;◈&lt;/span&gt;&lt;span&gt; Running&lt;/span&gt;&lt;span&gt; static&lt;/span&gt;&lt;span&gt; server&lt;/span&gt;&lt;span&gt; from&lt;/span&gt;&lt;span&gt; &quot;fauna-serverless-graphql/netlify&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;◈&lt;/span&gt;&lt;span&gt; Loaded&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; nasa.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;◈&lt;/span&gt;&lt;span&gt; Functions&lt;/span&gt;&lt;span&gt; server&lt;/span&gt;&lt;span&gt; is&lt;/span&gt;&lt;span&gt; listening&lt;/span&gt;&lt;span&gt; on&lt;/span&gt;&lt;span&gt; XXXXX&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;◈&lt;/span&gt;&lt;span&gt; Static&lt;/span&gt;&lt;span&gt; server&lt;/span&gt;&lt;span&gt; listening&lt;/span&gt;&lt;span&gt; to&lt;/span&gt;&lt;span&gt; XXXX&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   ┌─────────────────────────────────────────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   │&lt;/span&gt;&lt;span&gt;                                                 │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   │&lt;/span&gt;&lt;span&gt;   ◈&lt;/span&gt;&lt;span&gt; Server&lt;/span&gt;&lt;span&gt; now&lt;/span&gt;&lt;span&gt; ready&lt;/span&gt;&lt;span&gt; on&lt;/span&gt;&lt;span&gt; http://localhost:8888&lt;/span&gt;&lt;span&gt;   │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   │&lt;/span&gt;&lt;span&gt;                                                 │&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   └─────────────────────────────────────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;All your serverless functions will be visible from the &lt;code&gt;.netlify/functions&lt;/code&gt; route. Assuming your console says &lt;code&gt;http://localhost:8888&lt;/code&gt;, you should find a nice “hello galaxy!” message a &lt;code&gt;http://localhost:8888/.netlify/functions/nasa&lt;/code&gt;.&lt;/p&gt;&lt;h2&gt;Write a simple GraphQL resolver using Apollo lambda&lt;/h2&gt;&lt;p&gt;Now that you’re up-and-running, let’s jump into some GraphQL!&lt;/p&gt;&lt;p&gt;You may have noticed a static array of info under &lt;code&gt;utils/mockGalaxies.js&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;module&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;exports&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title: &lt;/span&gt;&lt;span&gt;&apos;Galaxy Centaurus A&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  description: &lt;/span&gt;&lt;span&gt;&apos;This image of the active galaxy Centaurus A was taken by NASA Galaxy Evolution Explorer on June 7, 2003. The galaxy is located 30 million light-years from Earth and is seen edge on, with a prominent dust lane across the major axis.&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  credits: &lt;/span&gt;&lt;span&gt;&apos;NASA/JPL/Caltech&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  imageUrl: &lt;/span&gt;&lt;span&gt;&apos;https://images-assets.nasa.gov/image/PIA04624/PIA04624~orig.jpg&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let’s use this as our “database” for now. Right off the bat, you’ll notice a common structure for each entry: &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;credits&lt;/code&gt;, and &lt;code&gt;imageUrl&lt;/code&gt;. We can consider this our GraphQL schema.&lt;/p&gt;&lt;p&gt;We’ll define this schema using the &lt;code&gt;gql&lt;/code&gt; helper provided by &lt;a href=&quot;https://www.npmjs.com/package/apollo-server-lambda&quot;&gt;apollo-server-lambda&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// netlify/functions/nasa.mjs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { gql } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;apollo-server-lambda&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; typeDefs&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; gql&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type Galaxy {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title: String,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  description: String,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  credits: String,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  imageUrl: String&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; handler&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This defines the structure of information that we’ll return in a moment.&lt;/p&gt;&lt;p&gt;Next, let’s define the “resolvers” for this endpoint. We just need a single resolver for this example: &lt;code&gt;galaxies&lt;/code&gt;. This returns our array of &lt;code&gt;Galaxy&lt;/code&gt; entries.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// netlify/functions/nasa.mjs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; typeDefs&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; gql&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type Query {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  galaxies: [Galaxy!]!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type Galaxy {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title: String,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  description: String,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  credits: String,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  imageUrl: String&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notice that we place this &lt;code&gt;galaxies&lt;/code&gt; resolver on the &lt;code&gt;Query&lt;/code&gt; type. We’ll reference this type when defining the &lt;code&gt;galaxies&lt;/code&gt; resolver.&lt;/p&gt;&lt;p&gt;Now that we have this schema defined, let’s start returning some data! We’ll use the &lt;code&gt;ApolloServer&lt;/code&gt; dependency here. This helps us:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Define what each resolver returns using JavaScript functions&lt;/li&gt;&lt;li&gt;Match this up with our &lt;code&gt;typeDefs&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Generate a request handler to use for our &lt;code&gt;export const handler&lt;/code&gt;. This is modeled to work with &lt;a href=&quot;https://aws.amazon.com/lambda/&quot;&gt;AWS Lambda&lt;/a&gt;, which is the backbone for Netlify serverless.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;We’ll write a resolver to return our &lt;code&gt;mockGalaxies&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// netlify/functions/nasa.mjs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { ApolloServer, gql } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;apollo-server-lambda&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; mockGalaxies &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;../../utils/mockGalaxies&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; typeDefs&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; gql&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type Galaxy {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  title: String,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  description: String,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  credits: String,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  imageUrl: String&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type Query {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  galaxies: [Galaxy!]!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; resolvers&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Query: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    galaxies&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; mockGalaxies&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; server&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; ApolloServer&lt;/span&gt;&lt;span&gt;({ typeDefs, resolvers })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; handler&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; server.&lt;/span&gt;&lt;span&gt;createHandler&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Try visiting &lt;a href=&quot;http://localhost:8888/.netlify/functions/nasa&quot;&gt;http://localhost:8888/.netlify/functions/nasa&lt;/a&gt; again. Now, you should see an entire playground interface to explore 👀&lt;/p&gt;&lt;p&gt;If you click on the “docs” tab, you’ll see that &lt;code&gt;galaxies&lt;/code&gt; resolver that we just defined. Clicking through reveals some information about the return value as well.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fgraphiql-netlify-serverless-demo.BJMY4pzi.png&quot; alt=&quot;GraphiQL UI demo. Docs tab is opened to reveal &amp;quot;galaxies&amp;quot; resolver.&quot; width=&quot;2000&quot; height=&quot;939&quot; /&gt;&lt;/p&gt;&lt;p&gt;This is automatically returned for all &lt;code&gt;GET&lt;/code&gt; requests to your GraphQL endpoint. Let’s try crafting a query in that lefthand menu to get our feet wet. I invite you to type this by hand instead of copy / pasting! You’ll be greeted by some handy autocomplete 🙃&lt;/p&gt;&lt;p&gt;For instance, let’s retrieve just the &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; fields from that array:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	galaxies {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		title&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		description&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hitting the “run” button should reveal that subset of info from our &lt;code&gt;mockGalaxies&lt;/code&gt;.&lt;/p&gt;&lt;h2&gt;Query this serverless endpoint from your Astro frontend&lt;/h2&gt;&lt;p&gt;So we’ve generated a playground to try out new queries. How can we use this inside a real-world application?&lt;/p&gt;&lt;p&gt;Head over to &lt;code&gt;www/src/pages/index.astro&lt;/code&gt; in our example project. You should see the following TODO at the top of the file:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; galaxies &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// TODO: fetch galaxy summary info&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Don’t worry &lt;em&gt;too&lt;/em&gt; much about the fancy templating being used at the bottom of the file. Just know that anything inside those &lt;code&gt;---&lt;/code&gt; blocks will be plain ole JavaScript that’s run whenever we build our site. You can always &lt;a href=&quot;https://docs.astro.build/en/getting-started/&quot;&gt;explore Astro’s getting started guide&lt;/a&gt; to go further 🚀&lt;/p&gt;&lt;p&gt;Let’s try generating an overview page of all the galaxies in our “database.” That view should look something like this once it’s working:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fastro-galaxy-homepage.BD3OOlpf.png&quot; alt=&quot;Galaxy homepage with 4 tiles, one for each galaxy. Each has a heading, description, and &amp;quot;take me there&amp;quot; link.&quot; width=&quot;2000&quot; height=&quot;1388&quot; /&gt;&lt;/p&gt;&lt;p&gt;☝️ You’ll notice we only need each galaxy’s title and description. Let’s write a GraphQL query to reflect this.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// www/src/pages/index.astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Layout &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;../layouts/base.astro&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; slugify &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;slugify&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; galaxies &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;meta&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	method: &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	body: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		query: &lt;/span&gt;&lt;span&gt;`{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			galaxies {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				title&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				description&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;galaxies &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.galaxies&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To summarize what’s going on:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;We make a &lt;code&gt;fetch&lt;/code&gt; request to our serverless function using the web-standard &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;fetch API&lt;/a&gt;&lt;/li&gt;&lt;li&gt;We use &lt;code&gt;import.meta.env.API_URL&lt;/code&gt; as the URL to request from. This is &lt;a href=&quot;https://docs.astro.build/en/guides/environment-variables/#:~:text=Astro%20uses%20Vite%20for%20environment,avilable%20in%20client%2Dside%20code.&quot;&gt;referencing an environment variable&lt;/a&gt; in our application defined under &lt;code&gt;www/.env-development&lt;/code&gt;. Tweak this to match wherever you’re running that Netlify development server from.&lt;/li&gt;&lt;li&gt;We use &lt;code&gt;POST&lt;/code&gt; as our method here. Remember, since we have a preference for what data we want, we need to “post” a body to our server.&lt;/li&gt;&lt;li&gt;We write our query as a string. Feel free to copy / paste the example request we wrote in the playground, placed under the &lt;code&gt;query&lt;/code&gt; key as shown.&lt;/li&gt;&lt;li&gt;We convert the response to &lt;a href=&quot;https://www.json.org/json-en.html&quot;&gt;JSON&lt;/a&gt; using &lt;code&gt;res.json()&lt;/code&gt;&lt;/li&gt;&lt;li&gt;We assign the response to our &lt;code&gt;galaxies&lt;/code&gt; variable. Feel free to add to add any &lt;code&gt;try / catch&lt;/code&gt; boundaries or error handling you want based on that &lt;code&gt;error&lt;/code&gt; object (&lt;a href=&quot;https://github.com/ben-holmes-blog-demos/faunadb-graphql-serverless-demos/blob/main/www/src/pages/index.astro&quot;&gt;example here&lt;/a&gt;).&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Assuming your Netlify server is still running from that &lt;code&gt;npm run ntl:dev&lt;/code&gt; command, you’ll need to open a second terminal window to see this frontend in action. Create a new window and run the following:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt; astro:dev&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You should see an output like &lt;code&gt;06:23 PM [astro] Local: http://127.0.0.1:3000/&lt;/code&gt;. Clicking this link, you’ll find a nice summary of galaxies to explore 🚀&lt;/p&gt;&lt;h3&gt;Generate routes for each galaxy&lt;/h3&gt;&lt;p&gt;You may have hit this error trying to click any of those homepage links:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[getStaticPaths] route pattern matched, but no matching static path found. (&lt;/span&gt;&lt;span&gt;/Galaxy-Centaurus-A&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That’s because, well, these routes don’t exist yet! Let’s generate unique pages for each galaxy to reveal more information. Head over to &lt;code&gt;www/src/pages/[galaxy].astro&lt;/code&gt;. Here, you’ll find a helper to generate routes from API data like so:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getStaticPaths&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // TODO: generate pages for each galaxy&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you come from NextJS, you’ll notice this is &lt;em&gt;very&lt;/em&gt; similar to &lt;a href=&quot;https://nextjs.org/docs/api-reference/data-fetching/get-static-paths&quot;&gt;their static path generator&lt;/a&gt;. This function should return an array of website pages for Astro to generate, each displaying the whole suite of &lt;code&gt;Galaxy&lt;/code&gt; info: &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;credits&lt;/code&gt;, and &lt;code&gt;imageUrl&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;We can write a similar &lt;code&gt;fetch&lt;/code&gt; query to that earlier example, this time requesting all 4 keys:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Layout &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;../layouts/base.astro&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; slugify &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;slugify&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getStaticPaths&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	// same format as before, now with a new query body&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;meta&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      method: &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      body: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        query: &lt;/span&gt;&lt;span&gt;`{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          galaxies {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            title&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            description&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            imageUrl&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            credits&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; data.galaxies.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;galaxy&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; ({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // params.galaxy -&amp;gt; the URL you can visit in your browser&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // ex. the galaxy with title &quot;Andromeda Galaxy&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // will be visible at http://127.0.0.1:3000/Andromeda-Galaxy&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      params: { galaxy: &lt;/span&gt;&lt;span&gt;slugify&lt;/span&gt;&lt;span&gt;(galaxy.title) },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // props -&amp;gt; all information available to our galaxy page&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // see the Astro.props being used below&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      props: galaxy,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;imageUrl&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;credits&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Astro.props&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This should generate unique URLs for all of our galaxies, and grab each galaxy’s info for display using &lt;code&gt;Astro.props&lt;/code&gt;. Now, clicking any of your homepage links should reveal a beautiful galaxy image like so:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fastro-galaxy-about-page.CGvHjyuX.png&quot; alt=&quot;Galaxy homepage with heading, banner image, and description.&quot; width=&quot;770&quot; height=&quot;943&quot; /&gt;&lt;/p&gt;&lt;h2&gt;Persist these galaxies in a database with Fauna&lt;/h2&gt;&lt;p&gt;This is great for demo purposes, but you probably can’t store all your information in a static &lt;code&gt;mockGalaxies&lt;/code&gt; file.&lt;/p&gt;&lt;p&gt;Let’s try throwing these galaxies in a Fauna database. To get the most up-to-date info, I recommend &lt;strong&gt;&lt;a href=&quot;https://docs.fauna.com/fauna/current/learn/quick_start/client_quick_start?lang=javascript#prerequisites?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=Ben_Holmes_Serverless&quot;&gt;visiting Fauna’s prerequisite doc section&lt;/a&gt;&lt;/strong&gt; to get started. This will walk you through setting up an account, generating your first database, and getting an &lt;strong&gt;API secret&lt;/strong&gt; for connecting to that database. Don’t worry, I’ll wait 😁&lt;/p&gt;&lt;p&gt;...&lt;/p&gt;&lt;p&gt;Got that secret key? Great! Let’s store this in a &lt;code&gt;.env&lt;/code&gt; file for our serverless function to use. Create a &lt;code&gt;.env&lt;/code&gt; file at the base of the project with the following contents:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;FAUNA_SECRET&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;secret-you-copied-from-fauna&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Also take note of the region used to generate this new project. Our starter code assumes you’ll use a US region. If not, feel free to update the “domain” field under &lt;code&gt;utils/getFaunaClient.js&lt;/code&gt;. &lt;a href=&quot;https://docs.fauna.com/fauna/current/drivers/connections?lang=javascript#connection-options&quot;&gt;See this documentation&lt;/a&gt; for properly setting the domain URL. Warning: If configured improperly, &lt;strong&gt;none of your queries will run 🚨&lt;/strong&gt;&lt;/p&gt;&lt;h3&gt;Load &lt;code&gt;mockGalaxies&lt;/code&gt; into your database&lt;/h3&gt;&lt;p&gt;Let’s “seed” this new database with our sample data. We’ve written a handy script under &lt;code&gt;utils/seed.js&lt;/code&gt; to handle this. If you’re curious about Fauna’s fancy FQL query syntax, we recommend checking &lt;a href=&quot;https://docs.fauna.com/fauna/current/tutorials/basics/?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=Ben_Holmes_Serverless&quot;&gt;the FQL fundamentals guide&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;You can run this script by executing:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt; ./utils/seed.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once this is finished, you should see a new &lt;code&gt;galaxies&lt;/code&gt; collection under the “collections” tab in your &lt;code&gt;dashboard.fauna.com&lt;/code&gt; window.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Ffauna-generated-collections.Dd0yvFx0.png&quot; alt=&quot;Fauna collections tab, with a list of galaxy JSON objects under the galaxies &amp;gt; documents heading.&quot; width=&quot;1726&quot; height=&quot;1008&quot; /&gt;&lt;/p&gt;&lt;h3&gt;Query this data from your GraphQL resolver&lt;/h3&gt;&lt;p&gt;With this data in place, we’re ready to start querying from our GraphQL resolver! Start be establishing a connection with your Fauna server. We included a handy helper function for this under &lt;code&gt;utils/getFaunaClient.js&lt;/code&gt;, but here’s the code for the sake of completeness:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;Client&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;faunadb&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// set up our fauna secret from that .env file&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;dotenv&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;module&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;exports&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getFaunaClient&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Client&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    secret: process.env.&lt;/span&gt;&lt;span&gt;FAUNA_SECRET&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		// whichever domain you (hopefully!) configured earlier&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		// docs: https://docs.fauna.com/fauna/current/drivers/connections?lang=javascript#connection-options&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    domain: &lt;/span&gt;&lt;span&gt;&quot;db.us.fauna.com&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, call this helper from the top of our &lt;code&gt;nasa.mjs&lt;/code&gt; function handler:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// netlify/functions/nasa.mjs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { ApolloServer, gql } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;apollo-server-lambda&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { query &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; q } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;faunadb&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; getFaunaClient &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;../../utils/getFaunaClient&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; faunaClient&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getFaunaClient&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, let’s query our &lt;code&gt;galaxies&lt;/code&gt; collection from our &lt;code&gt;galaxies&lt;/code&gt; GraphQL resolver. If you want to learn Fauna’s FQL language, I highly recommend trying the “shell” tab from &lt;code&gt;dashboard.fauna.com&lt;/code&gt; first. You can use this for sandboxing queries before their implementation. I was personally quite new to FQL, so it was pretty helpful to try out the &lt;code&gt;Document&lt;/code&gt; and &lt;code&gt;Lambda&lt;/code&gt; helpers 🙃&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Ffauna-shell-demo.D51Rfapl.png&quot; alt=&quot;Fauna shell, with a paginated query for all galaxies and resulting JSON output.&quot; width=&quot;2000&quot; height=&quot;1395&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Sample output from the “shell.” Check &lt;a href=&quot;https://docs.fauna.com/fauna/current/tutorials/basics/?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=Ben_Holmes_Serverless&amp;amp;lang=javascript#documents-function&quot;&gt;Fauna’s FQL fundamentals&lt;/a&gt; to learn more about querying documents.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;☝️ As you can see, this test query returned our galaxy data as expected. We can use Fauna’s JavaScript library to translate this into our demo app:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// netlify/functions/nasa.mjs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { query &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; q } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;faunadb&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; resolvers&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Query: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    async&lt;/span&gt;&lt;span&gt; galaxies&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; galaxiesRaw&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; faunaClient.&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        q.&lt;/span&gt;&lt;span&gt;Map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          q.&lt;/span&gt;&lt;span&gt;Paginate&lt;/span&gt;&lt;span&gt;(q.&lt;/span&gt;&lt;span&gt;Documents&lt;/span&gt;&lt;span&gt;(q.&lt;/span&gt;&lt;span&gt;Collection&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;galaxies&quot;&lt;/span&gt;&lt;span&gt;))),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          q.&lt;/span&gt;&lt;span&gt;Lambda&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;galaxyRef&quot;&lt;/span&gt;&lt;span&gt;, q.&lt;/span&gt;&lt;span&gt;Get&lt;/span&gt;&lt;span&gt;(q.&lt;/span&gt;&lt;span&gt;Var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;galaxyRef&quot;&lt;/span&gt;&lt;span&gt;)))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				// Fauna returns each object on a nested “data” key&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				// Let’s flatten this&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; galaxiesRaw.data.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;galaxyRaw&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; galaxyRaw.data)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`The fauna galaxies query returned malformed data: ${&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;galaxiesRaw&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you check out our Astro dev server from earlier, we should see... exactly the same output 😁 But now we’re free to add more documents in the future, or even create a &lt;code&gt;/upload&lt;/code&gt; route to submit new galaxies.&lt;/p&gt;&lt;p&gt;Now, your code is ready to ship! If you hit any rough patches along the way, or want to see everything in its final form...&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/ben-holmes-blog-demos/faunadb-graphql-serverless-demos&quot;&gt;&lt;strong&gt;Check our finished solution on GitHub →&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;Can you use Fauna’a GraphQL resolvers instead?&lt;/h2&gt;&lt;p&gt;You might be aware that Fauna &lt;a href=&quot;https://docs.fauna.com/fauna/current/learn/quick_start/gql_quick_start?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=Ben_Holmes_Serverless#create&quot;&gt;has a built-in GraphQL API&lt;/a&gt; you can query. This is a &lt;em&gt;great&lt;/em&gt; option to consider as well! It’ll save you from writing these Apollo resolvers + serverless functions yourself, and it should ensure more complex queries stay efficient.&lt;/p&gt;&lt;p&gt;So why did we write these resolvers by hand? There’s a couple reasons:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;I’m not sure what database you plan to use.&lt;/strong&gt; If you’re reading this article coming from a non-Fauna storage option, I want to show how you can build your own GraphQL + serverless flow using Apollo. Fauna’s GraphQL setup is a natural next step if you like what you see.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;It’s helpful to explore serverless hosting options.&lt;/strong&gt; Fauna hosts the serverless GraphQL backend for you, which &lt;em&gt;is&lt;/em&gt; extremely convenient for spinning up new projects. Still, I’d assume most projects with mid-to-high complexity need to manage their own infrastructure in some way. Whether your stitching together different microservices, or &lt;a href=&quot;https://www.youtube.com/watch?v=yuxd2kurpzk&quot;&gt;exploring edge computing for distribution&lt;/a&gt;, it’s best to understand your options before using the batteries-included option.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Deploy this serverless function for production use&lt;/h2&gt;&lt;p&gt;Finally, let’s try deploying our serverless endpoint to a “live” server. We have a couple pre-written commands to try here:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;npm run ntl:deploy&lt;/code&gt; → Deploy to a URL of your choosing&lt;/li&gt;&lt;li&gt;&lt;code&gt;npm run ntl:deploy-preview&lt;/code&gt; → Deploy to a randomized test URL&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I’d recommend trying the &lt;code&gt;deploy-preview&lt;/code&gt; first. This will run &lt;code&gt;netlify env:import .env&lt;/code&gt; to ensure &lt;code&gt;FAUNA_SECRET&lt;/code&gt; is visible from your serverless function. You’ll see an output like this in your console if all goes well:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.---------------------------------------------------------.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;             Imported&lt;/span&gt;&lt;span&gt; environment&lt;/span&gt;&lt;span&gt; variables&lt;/span&gt;&lt;span&gt;              |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;---------------------------------------------------------&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;     Key&lt;/span&gt;&lt;span&gt;      |&lt;/span&gt;&lt;span&gt;                  Value&lt;/span&gt;&lt;span&gt;                   |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;--------------&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;------------------------------------------&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; FAUNA_SECRET&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; secret-you-copied-from-fauna&lt;/span&gt;&lt;span&gt;             |&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&apos;---------------------------------------------------------&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, it will upload a live deploy preview under your Netlify account. You should be free to explore this preview by visiting &lt;code&gt;random-url.netlify.app/.netlify/functions/nasa&lt;/code&gt;.&lt;/p&gt;&lt;h2&gt;Deploy your frontend for production use&lt;/h2&gt;&lt;p&gt;With this serverless function live, you’re ready to build your static website as well.&lt;/p&gt;&lt;p&gt;Remember that &lt;code&gt;www/.env.development&lt;/code&gt; from earlier? This set our &lt;code&gt;API_URL&lt;/code&gt; to reference our local Netlify instance. To configure your production builds, you’ll need a &lt;code&gt;www/.env.production&lt;/code&gt; just alongside this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;https&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;//where-you-deployed-your-serverless-fn/.netlify/functions/nasa&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, you’re ready to run either of the following scripts:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;npm run astro:preview&lt;/code&gt; → generate a local preview of your production build&lt;/li&gt;&lt;li&gt;&lt;code&gt;npm run astro:deploy&lt;/code&gt; → deploy your website for production&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;I hope this walkthrough opened your mind on some new tooling! If there’s any takeaways you find from this article, I hope they’re along the lines of:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;GraphQL + serverless are a great pairing for frontend-leaning devs looking to ship quickly and cheaply&lt;/li&gt;&lt;li&gt;You can absolutely deploy GraphQL to a serverless function, all with minimal effort thanks to packages like &lt;a href=&quot;https://github.com/apollographql/apollo-server/tree/main/packages/apollo-server-lambda&quot;&gt;apollo-server-lambda&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://docs.fauna.com/fauna/current/learn/quick_start/gql_quick_start?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=Ben_Holmes_Serverless&quot;&gt;Fauna’s built-in GraphQL endpoints&lt;/a&gt; are worth exploring for a batteries-included option to backend management&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt; is pretty helpful for hosting frontends &lt;em&gt;and&lt;/em&gt; backends these days&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://api.nasa.gov/&quot;&gt;NASA’s public APIs&lt;/a&gt; are awesome for interstellar imagery 🪐&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;As always, there’s no “silver bullet” to building applications. So, keep sampling this renaissance of new tooling and find what sticks!&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Understanding single page apps &amp; client-side routing</title><link>https://bholmes.dev/blog/spas-clientside-routing/</link><guid isPermaLink="true">https://bholmes.dev/blog/spas-clientside-routing/</guid><description>Let&apos;s get some clarity on what single page apps are, how they work, and where they differ from server-side routing.</description><pubDate>Tue, 06 Jul 2021 20:01:36 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;&quot;SPA&quot; has been a hot topic ever since &quot;modern&quot; JS frameworks like React hit the scene. They promise all sorts of benefits like &lt;em&gt;dynamic user interactions,&lt;/em&gt; &lt;em&gt;lightning-fast load times,&lt;/em&gt; &lt;em&gt;solving world hunger,&lt;/em&gt; etc. (okay that last one is a stretch...)&lt;/p&gt;&lt;p&gt;But have you ever stopped to wonder what&apos;s &lt;em&gt;actually&lt;/em&gt; going on behind the scenes? And if you&apos;re &lt;em&gt;not&lt;/em&gt; using an SPA, are you stuck in the past? Well, let&apos;s explore:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;🤔 How non-SPAs work + how they perform on the modern web (spoiler, they&apos;re a great choice as always!)&lt;/li&gt;&lt;li&gt;⚙️ The key benefits and inner workings of an SPA&lt;/li&gt;&lt;li&gt;🍔 A side-by-side visual comparison feat. juicy burgers&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Onwards!&lt;/p&gt;&lt;h2&gt;🤔 First off, how do &lt;em&gt;non-&lt;/em&gt; SPAs work?&lt;/h2&gt;&lt;p&gt;Web development has been soaking in acronym soup lately, so I think it&apos;s worth clarifying what &lt;em&gt;isn&apos;t&lt;/em&gt; an SPA first 🙃&lt;/p&gt;&lt;p&gt;If your site isn&apos;t an SPA, you&apos;re likely using what&apos;s called &lt;strong&gt;&quot;server-based routing.&quot;&lt;/strong&gt; A metaphor is in order here. Say you&apos;re at a formal, sit-down restaurant with a waiter (a server if you will 😉). If you want food, you&apos;ll have to:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Ask the waiter for the food you want&lt;/li&gt;&lt;li&gt;Wait a moment for the dish to get prepared&lt;/li&gt;&lt;li&gt;Receive your finished plate once it&apos;s done&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;This is how all websites work when you first visit them. You ask for what you want (visiting a URL like &lt;code&gt;https://breakfast.club)&lt;/code&gt;), wait for the server to get back to you (loading spinner), then enjoy your &quot;meal&quot; once it&apos;s ready (the page is done loading! 🎉)&lt;/p&gt;&lt;p&gt;&lt;strong&gt;But what if you want to order dessert?&lt;/strong&gt; Heading back to our restaurant scenario, you&apos;ll go through the same flow as last time:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Ask the waiter for the yummiest dessert&lt;/li&gt;&lt;li&gt;Hand them your dirty plates&lt;/li&gt;&lt;li&gt;Wait a moment for the dish to get prepared&lt;/li&gt;&lt;li&gt;Receive a shiny new bowl with your ice cream sundae 🍨&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;There&apos;s an important bit I want to reiterate there: &lt;strong&gt;you hand over your dirty plates, and get back a brand new one.&lt;/strong&gt; This is why you&apos;ll see a little loading bar re-appear every time you jump to a new page. Here&apos;s an example of 11ty&apos;s documentation using server-based routing:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fspas-11ty-docs-page-reload.CpFbTiKW.gif&quot; alt=&quot;Clicking through pages of 11ty&apos;s documentation site&quot; width=&quot;600&quot; height=&quot;419&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Watch the loading bar engage whenever we click a link&lt;/em&gt;&lt;/p&gt;&lt;p&gt;At first, it looks like you&apos;re still on the same page whenever you click those navigation links, and the browser&apos;s only refreshes the bits that change (the documentation).&lt;/p&gt;&lt;p&gt;&lt;strong&gt;🚨 But that&apos;s not really the case!&lt;/strong&gt; Whenever you click a link, your browser is &quot;clearing your plate&quot; (removing &lt;em&gt;everything&lt;/em&gt; from the page) and pulling in a fresh meal, navigation and all. So you&apos;re really reloading all those pieces of navigation every time you visit a new page, even if they were &lt;em&gt;already&lt;/em&gt; on your plate to begin with.&lt;/p&gt;&lt;h3&gt;Wait, is that wasteful?&lt;/h3&gt;&lt;p&gt;It may sound like the browser&apos;s doing a lot of extra work here! Although this &lt;em&gt;is&lt;/em&gt; still an issue that SPAs can help with, it&apos;s not as problematic as it used to be. Here&apos;s a few efficiency points to consider:&lt;/p&gt;&lt;h4&gt;1. HTML is super cheap to load and render&lt;/h4&gt;&lt;p&gt;Even though the browser is &quot;re-painting&quot; the page from scratch every time, it only takes a few milliseconds to load that HTML skeleton. The truly expensive bits are the styles, scripts, and images the browser needs to fetch, which leads us to...&lt;/p&gt;&lt;h4&gt;2. Shared resources don&apos;t need to get re-loaded&lt;/h4&gt;&lt;p&gt;For instance, say every documentation page pulls in the same CSS file: &lt;code&gt;&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;documentation.css&quot;&amp;gt;&lt;/code&gt;. When you click another link that &lt;em&gt;also&lt;/em&gt; pulls this CSS, the browser&apos;s smart enough to say &quot;oh, I loaded this already! I&apos;ll just use that and apply it to the page.&quot; The same goes for images and fonts as well.&lt;/p&gt;&lt;p&gt;💡 &lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is all thanks to caching. If you want to go deeper, the Remix team has an excellent walkthrough on caching documents, resources, and more &lt;a href=&quot;https://www.youtube.com/watch?v=3XkU_DXcgl0&quot;&gt;over here&lt;/a&gt;&lt;/em&gt; 😄&lt;/p&gt;&lt;p&gt;So the actual &lt;em&gt;loading&lt;/em&gt; of these shared resources isn&apos;t a problem. But what about &lt;em&gt;painting&lt;/em&gt; those resources to the page over and over again? This brings us to...&lt;/p&gt;&lt;h4&gt;3. The next page only appears when it&apos;s ready&lt;/h4&gt;&lt;p&gt;Back in the day, there was a risk you&apos;d see a flash of white nothingness between &quot;clearing the plate&quot; and &quot;receiving the new one.&quot; But &lt;a href=&quot;https://developers.google.com/web/updates/2019/05/paint-holding&quot;&gt;modern browsers have pretty much resolved this&lt;/a&gt;! In short, web browsers wait for the signal that the next page isn&apos;t just &quot;done loading,&quot; but is also ready for you to view and interact with. Think of this like bringing out your plate when it&apos;s ready to eat, instead of bringing you a plate of ingredients you have to assemble yourself.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fspa-hibachi-rendering.CmHuvJq3.gif&quot; alt=&quot;Chef tossing knives around a hibachi grill&quot; width=&quot;640&quot; height=&quot;360&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Hibachi rendering:&lt;/strong&gt; showing you the page painting process before it&apos;s ready to eat (it&apos;s less cool when browsers do it)&lt;/em&gt; 😉&lt;/p&gt;&lt;p&gt;This especially helps out pages that rely on blocking resources like JavaScript to render everything on the page. Here&apos;s a quick before-and-after shot by the Chrome team when they launched this concept 🚀&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fspa-flash-of-white.Cp5Ob25l.gif&quot; alt=&quot;Comparison of the flash of white from loading a JS-intensive page before and after the Google Chrome update&quot; width=&quot;600&quot; height=&quot;333&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://developers.google.com/web/updates/2019/05/paint-holding&quot;&gt;Source article&lt;/a&gt; by Addy Osmani&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; The browser won&apos;t &lt;strong&gt;always&lt;/strong&gt; wait for the next page to be interactive before showing it; It&apos;ll still show the half-finished page if you&apos;re on a slow internet connection and the browser decides it&apos;s waited too long. Still, for snappier sites like that &lt;a href=&quot;https://www.11ty.dev/docs/&quot;&gt;11ty documentation&lt;/a&gt; from earlier, it shouldn&apos;t be a problem!&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;💁 Now, let&apos;s talk single page apps&lt;/h2&gt;&lt;p&gt;So how do SPAs compare? Well, let&apos;s revisit that restaurant example from before. When you first visit a site using the SPA approach, everything works pretty much the same:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Ask the server for the food you want (visit &lt;code&gt;https://spa-breakfast.club&lt;/code&gt;)&lt;/li&gt;&lt;li&gt;Wait a moment for the dish to get prepared (browser loading spinner)&lt;/li&gt;&lt;li&gt;Receive your finished plate once it&apos;s done (the page is done loading! 🎉)&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Now the interesting part comes when you &lt;em&gt;go back for seconds.&lt;/em&gt; When you click a link on an SPA, it replaces the typical, server-based routing with &lt;strong&gt;clientside routing&lt;/strong&gt; 😮 In other words, we process all link requests using JavaScript we wrote ourselves, instead of sending those requests to the server right away.&lt;/p&gt;&lt;p&gt;Here&apos;s a code snippet to get your brain moving 🧠&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/desert&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;Go eat desert&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	document.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;click&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // if you clicked on an A-nchor tag (link)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        event.target.tagName &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;A&apos;&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // and you&apos;re going to a page on this domain (like /desert)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        event.target.origin &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; location.origin&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // don&apos;t ask the server for that resource!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        event.&lt;/span&gt;&lt;span&gt;preventDefault&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // instead, we&apos;ll go fetch the resource ourselves&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;https://buffet.table/desert&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // ...convert that response to something we can work with&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; htmlString&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; desert&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; DOMParser&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          .&lt;/span&gt;&lt;span&gt;parseFromString&lt;/span&gt;&lt;span&gt;(htmlString, &lt;/span&gt;&lt;span&gt;&apos;text/html&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // ...and do something with that desert element&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // ex. append desert to our &quot;plate&quot; in the DOM&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;.my-plate&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;appendChild&lt;/span&gt;&lt;span&gt;(desert)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pretty weird, right? This is the super-simplified crux of an SPA: &lt;strong&gt;you never truly &quot;leave&quot; the page you started on.&lt;/strong&gt; Instead, you intercept all future requests (link clicks) and handle the fetching yourself. You&apos;ll often do this using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;the &lt;code&gt;fetch&lt;/code&gt; API&lt;/a&gt; native to all modern browsers as demo-d above.&lt;/p&gt;&lt;p&gt;This is why I&apos;d think of SPAs and clientside routing like a &lt;strong&gt;buffet&lt;/strong&gt;. Instead of ordering your meal and waiting for it to get prepared, you can get out of your seat and grab that pre-prepared food yourself!&lt;/p&gt;&lt;h3&gt;Key benefits to this approach&lt;/h3&gt;&lt;p&gt;There are 2 major benefits to single page apps that clientside routing unlocks 🔓&lt;/p&gt;&lt;p&gt;&lt;strong&gt;First, load-time efficiency &lt;em&gt;can&lt;/em&gt; go up 🚀&lt;/strong&gt; I say &quot;can&quot; because of all the serverside routing optimization I mentioned previously (which may cancel out any performance gains). But there &lt;em&gt;is&lt;/em&gt; a noticeable difference for resource-hungry frameworks like React, Vue, and Svelte. All of these use some form of clientside routing to keep JavaScript load times to a minimum.&lt;/p&gt;&lt;p&gt;For instance, if I went from one page of a React app to another using serverside routing, the browser would &lt;em&gt;re-parse and re-render the whole page&lt;/em&gt; using JavaScript! This can severely impact your &quot;time to interactive,&quot; an accessibility concern you can learn more about &lt;a href=&quot;https://web.dev/interactive/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Second, you can now build dynamic, app-like interactions ⚙️&lt;/strong&gt; Animated page transitions are the easiest to point out here. Since you&apos;re fully in control of loading new content and applying it to the page, you can pull off all sorts of CSS trickery like cross-fades, pop-up modals, tab sliders, and more. Here&apos;s an example from my personal site using clientside routing:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fspa-bholmesdev-page-transitions.dUfr2u0S.gif&quot; alt=&quot;Preview of page transitions clicking links on bholmes.dev&quot; width=&quot;800&quot; height=&quot;749&quot; /&gt;&lt;/p&gt;&lt;h2&gt;🍔 A juicy visual comparison&lt;/h2&gt;&lt;p&gt;Now that we&apos;ve introduced SPAs + clientside routing as a concept, lets see a side-by-side comparison with serverside routing.&lt;/p&gt;&lt;p&gt;Say you receive your order for a juicy burger that&apos;s &lt;a href=&quot;https://medium.com/@samsonspaddockau/whats-the-difference-between-rare-medium-and-well-done-steak-fe463e0f960f&quot;&gt;cooked medium rare&lt;/a&gt;, but would rather have a patty that&apos;s well done.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;We&apos;ll imagine this moment as &quot;clicking a link,&quot;&lt;/strong&gt; requesting to go from &lt;code&gt;/medium-rare-burger&lt;/code&gt; to &lt;code&gt;/well-done-burger&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Here&apos;s what that &quot;click&quot; may do with a server-based approach:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fspa-ssr-demo.HQICIHph.gif&quot; alt=&quot;Step-by-step serverside routing process: 1. Medium rare hamburger is returned, 2. The HTML document is emptied out while the new one loads, 3. The well done hamburger is applied to our page&quot; width=&quot;800&quot; height=&quot;629&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Animation showing the 3 step rendering process&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Then, here&apos;s how clientside routing may handle the request:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fspa-csr-demo.CS5qG13v.gif&quot; alt=&quot;Step-by-step clientside routing process: 1. Medium rare hamburger is returned, 2. We request a well done burger using the fetch API, 3. We massage the response, 4. We pluck out the &amp;quot;patty&amp;quot; element and apply it to our current page&quot; width=&quot;800&quot; height=&quot;629&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Animation showing our new 4 step rendering process&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Notice that we never clear our page in the SPA approach! We just request the resource (a well-done burger), pick out the pieces we want to add to our page (the patty), and perform the DOM manipulation with JavaScript.&lt;/p&gt;&lt;p&gt;This doesn&apos;t have much performance benefits when we&apos;re talking HTML files. But if that HTML has some JavaScript and styles attached to it that we can &lt;em&gt;also&lt;/em&gt; fetch (like, say, a React component), there&apos;s a lot of room for performance gains.&lt;/p&gt;&lt;h2&gt;Wrapping up: so which should I choose?&lt;/h2&gt;&lt;p&gt;Although SPAs seem like the &quot;silver bullet&quot; for any website, there&apos;s a case to be made for either approach.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The most obvious win for server-side routing is, &lt;strong&gt;well, it&apos;s just simpler.&lt;/strong&gt; No need to write and maintain all those click listeners; just let the browser do the serving for you. Yes, you&apos;ll often use a framework-specific library for SPAs like &lt;a href=&quot;https://reactrouter.com/&quot;&gt;React Router&lt;/a&gt;, but learning and updating JS libraries is always more overhead.&lt;/li&gt;&lt;li&gt;The second win for server-side is &lt;strong&gt;no accessibility worries.&lt;/strong&gt; When you handle all the routing clientside, you run the risk of hurting screenreader and keyboard experiences. For instance, you&apos;ll need to alert screenreader users that new content has appeared on the page whenever they click a link. And for keyboard-ists, you need to make sure their &lt;a href=&quot;https://bholmes.dev/blog/accessible-show-hide-animations/&quot;&gt;focusing the right element&lt;/a&gt; when something new swipes into view.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;☝️ But if you&apos;re addressing these concerns confidently (or using a robust library to do it for you), SPAs are a great way to go!&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Grabbing subsets of JS object properties with... GraphQL?</title><link>https://bholmes.dev/blog/query-json-objects-graphql/</link><guid isPermaLink="true">https://bholmes.dev/blog/query-json-objects-graphql/</guid><description>It&apos;s easy to grab keys out of JSON objects, but what if we want to grab entire, nested sets of object keys without destructuring?</description><pubDate>Mon, 07 Jun 2021 20:01:36 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;Messing with JavaScript objects is pretty easy these days. Destructuring syntax is convenient and the spread &lt;code&gt;...&lt;/code&gt; operator helps with merging objects together. But what about grabbing just... a portion of an object?&lt;/p&gt;&lt;p&gt;This question deserves some visuals. Let&apos;s jump into the problem we&apos;re trying to solve, and a flexible solution we can add to any existing JavaScript project 💪&lt;/p&gt;&lt;h2&gt;What we want&lt;/h2&gt;&lt;p&gt;Say I have a big document of structured data. Without manually writing a new object by hand, I just want to pull the little slices that I actually care about.&lt;/p&gt;&lt;p&gt;Here&apos;s one such scenario:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fproblem.ByoySCmq.png&quot; alt=&quot;A side-by-side comparison of 2 JavaScript objects. Under &amp;quot;what we have,&amp;quot; there&apos;s an object with the keys &amp;quot;weekOldPasta&amp;quot; (with subways noodleSogginess, numMeatballs, and isEdible) and &amp;quot;panSearedSalmon&amp;quot; (with subkeys oilUsed, numSpices, and isEdible). On the right side under &amp;quot;what we want,&amp;quot; we see the same object as before, but only using the &amp;quot;isEdible&amp;quot; subkey under weekOldPasta and panSearedSalmon. The other subkeys are grayed out.&quot; width=&quot;1400&quot; height=&quot;760&quot; /&gt;&lt;/p&gt;&lt;p&gt;In this case, we want a copy of our original fridge, but we only care about those &lt;code&gt;isEdible&lt;/code&gt; subkeys.&lt;/p&gt;&lt;p&gt;My gut reaction is to reach for some declarative tools in my &lt;a href=&quot;http://es6-features.org/#Constants&quot;&gt;ES6&lt;/a&gt; arsenal. &lt;strong&gt;Object destructuring&lt;/strong&gt; comes to mind at first:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; whatsInMyFridge&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  weekOldPasta: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const { weekOldPasta: { isEdible: pastaIsEdible },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	panSearedSalmon: { isEdible: panSearedSalmonIsEdible }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; whatsInMyFridge&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There&apos;s a few issues with this:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;We can&apos;t easily destructure keys of the same name. Notice I had to convert each &lt;code&gt;isEdible&lt;/code&gt; variable to the verbose &lt;code&gt;pastaIsEdible&lt;/code&gt; and &lt;code&gt;panSearedSalmonIsEdible&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Destructuring leads to some pretty gnarly code as it gets more complex. Just with a few keys, we&apos;re already hitting multi-line { curly hell }.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;And most of all, &lt;strong&gt;we still have to build our new object at the end!&lt;/strong&gt; Our destructuring statement &lt;em&gt;actually&lt;/em&gt; just made some one-off variables for each key in the object. We&apos;ll still have to do this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; whatsEdible&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  weekOldPasta: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    isEdible: pastaIsEdible,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  panSearedSalmon: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    isEdible: panSearedSalmonIsEdible,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...which is hardly better than just writing the object from scratch 😢&lt;/p&gt;&lt;p&gt;What we really want is some magical syntax for &lt;em&gt;just the keys&lt;/em&gt; we want to retrieve. Something like this really:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;whatsInMyFridge.&lt;/span&gt;&lt;span&gt;giveMeTheseKeys&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	weekOldPasta {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		isEdible&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	panSearedSalmon {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		isEdible&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}) &lt;/span&gt;&lt;span&gt;// -&amp;gt; a beautiful formatted JS object&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;📈 Enter: GraphQL&lt;/h2&gt;&lt;p&gt;If you&apos;ve worked with GraphQL before, you probably noticed how close that example comes to a GraphQL query!&lt;/p&gt;&lt;p&gt;A brief rundown for those unfamiliar: GraphQL is a &quot;querying&quot; language originally built for API calls. It was mainly born from the frustrations with REST requests, as API endpoints had to &lt;em&gt;predict&lt;/em&gt; all the data a client might want to retrieve.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://docs.github.com/en/graphql/guides/migrating-from-rest-to-graphql&quot;&gt;GitHub recently migrated to GraphQL&lt;/a&gt; because of this. Imagine this scenario:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;User A wants to get information about their GitHub profile. They want to send off a username and get back the accounts &lt;strong&gt;name&lt;/strong&gt; and &lt;strong&gt;profile picture&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;User B also wants some GitHub profile info. However, they&apos;re looking for a different set of info: the list of &lt;strong&gt;user recovery emails&lt;/strong&gt; and their &lt;strong&gt;personal bio&lt;/strong&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;As you could imagine, User C might want a new combination of fields, as might users D-Z. So instead of returning a massive JSON payload to satisfy everyone, GitHub exposed a GraphQL API for you to describe &lt;em&gt;exactly&lt;/em&gt; which fields you want.&lt;/p&gt;&lt;p&gt;Here&apos;s how User A might request a name and profile picture as part of their request body &lt;em&gt;This is from demo purposes, and won&apos;t actually work if you send to GitHub&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	userProfile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;email&lt;/span&gt;&lt;span&gt;: &apos;&lt;/span&gt;&lt;span&gt;10xengineer&lt;/span&gt;&lt;span&gt;@genius&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;club&lt;/span&gt;&lt;span&gt;&apos;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		name&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		picture&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			src&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			alt&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...And GitHub will &quot;fill in the blanks&quot; by providing values to those requested keys. As you can imagine, this syntax is flexible enough to use on &lt;em&gt;any&lt;/em&gt; blob of JSON you want to filter down 👀&lt;/p&gt;&lt;h2&gt;📖 Applying GraphQL to reading JSON objects&lt;/h2&gt;&lt;p&gt;💡 &lt;strong&gt;TLDR:&lt;/strong&gt; If you want the final solution without all the walkthrough, jump down to &lt;a href=&quot;https://bholmes.dev/#link-the-finished-product&quot;&gt;the finished product&lt;/a&gt;!&lt;/p&gt;&lt;p&gt;Let&apos;s figure out how to use that fancy syntax for our use case. The biggest question to resolve is &quot;how do we interpret a GraphQL query in JS land?&quot; Sadly, there isn&apos;t a nice &quot;plain JS&quot; solution, so we &lt;em&gt;will&lt;/em&gt; be reaching for a library here.&lt;/p&gt;&lt;p&gt;Go ahead and install this &lt;a href=&quot;https://www.npmjs.com/package/graphql-query-to-json&quot;&gt;graphql-query-to-json package&lt;/a&gt;. It &lt;em&gt;does&lt;/em&gt; have a fair amount of sub-dependencies like the core &lt;a href=&quot;https://www.npmjs.com/package/graphql&quot;&gt;graphql package&lt;/a&gt; and the complimentary &lt;a href=&quot;https://www.npmjs.com/package/json-to-graphql-query&quot;&gt;json-to-graphql-query&lt;/a&gt;, so if that bothers you... my apologies 😢&lt;/p&gt;&lt;p&gt;Let&apos;s see what we get from our old &quot;what&apos;s edible in my fridge&quot; request:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;graphQlQueryToJson&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;graphql-query-to-json&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// or if you prefer: import { graphQlQueryToJson } from &apos;graphql-query-to-json&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; asJson&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; graphQlQueryToJson&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  weekOldPasta {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    isEdible&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  panSearedSalmon {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    isEdible&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(asJson)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/* 👇&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  query: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    weekOldPasta: { isEdible: true },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    panSearedSalmon: { isEdible: true }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Neat! Toss in a string, get back a JS object. You&apos;ll notice that it wraps our requested object with the &lt;code&gt;query&lt;/code&gt; key. This &lt;em&gt;would&lt;/em&gt; be useful if we were sending this request to an API, but for our purposes, we&apos;ll just ignore that key in our helper function. It also stubs out any unknown key values with &lt;code&gt;true&lt;/code&gt;, which we&apos;ll use to track down unfilled values later 👀&lt;/p&gt;&lt;h3&gt;Traversing our query&lt;/h3&gt;&lt;p&gt;With this JS object in hand, it&apos;s time to walk through all the keys and figure out which values to fill in. Let&apos;s start with a simple example that only goes 1 level of keys deep:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; myFridge&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	numEggs: &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	pintsOfIceCream: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	degreeUnits: &lt;/span&gt;&lt;span&gt;&apos;celsius&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; whatIWant&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	numEggs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	degreeUnits&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// grab the &quot;.query&quot; straight away, since we won&apos;t need that nested key&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; whatIWantAsJson&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; graphQlQueryToJson&lt;/span&gt;&lt;span&gt;(whatIWant).query&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 👉 { numEggs: true, degreeUnits: true }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we have our set of keys (&lt;code&gt;numEggs&lt;/code&gt; and &lt;code&gt;degreeUnits&lt;/code&gt;) each with a value of &lt;code&gt;true&lt;/code&gt;. To assign our actual values in place of those &lt;code&gt;true&lt;/code&gt; flags, we can&lt;/p&gt;&lt;ol&gt;&lt;li&gt;loop through all the object keys in &lt;code&gt;whatIWantAsJson&lt;/code&gt;, and&lt;/li&gt;&lt;li&gt;assign values from the same key in &lt;code&gt;myFridge&lt;/code&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// loop over the object keys...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; Object.&lt;/span&gt;&lt;span&gt;keys&lt;/span&gt;&lt;span&gt;(whatIWantAsJson)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	// and if that key has a value of &quot;true&quot;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (whatIWantAsJson[key] &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		// replace &quot;true&quot; with the value from myFridge&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		whatIWantAsJson[key] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; myFridge[key]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(whatIWantAsJson)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 👉 { numEggs: 5, degreeUnits: &apos;celsius&apos; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Handling nested objects&lt;/h3&gt;&lt;p&gt;This basic loop handles 1 level of nesting. But what if we have a request like this?&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // level 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  weekOldPasta {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	// level 2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    isEdible&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For this, we&apos;ll need a way to run our loop over &lt;code&gt;Object.keys&lt;/code&gt; for &lt;em&gt;every level&lt;/em&gt; of keys in our object. Get ready to put on your computer science hat, because we&apos;re using &lt;strong&gt;recursion&lt;/strong&gt; 😨&lt;/p&gt;&lt;p&gt;Pay attention to this new &lt;code&gt;else&lt;/code&gt; statement we&apos;re adding:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// wrap our loop in a function we can call&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; assignValuesToObjectKeys&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;whatIWant&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;myFridge&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; Object.&lt;/span&gt;&lt;span&gt;keys&lt;/span&gt;&lt;span&gt;(whatIWant)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		if&lt;/span&gt;&lt;span&gt; (whatIWant[key] &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			whatIWant[key] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; myFridge[key]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			// if the value isn&apos;t &quot;true&quot;, we must have found a nested object&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			// so, we&apos;ll call this same function again, now starting from&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			// the nested object inside whatIWant&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			assignValuesToObjectKeys&lt;/span&gt;&lt;span&gt;(whatIWant[key], myFridge[key])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is a classic example of a &lt;a href=&quot;https://www.freecodecamp.org/news/learn-recursion-in-javascript-by-example/&quot;&gt;recursive function&lt;/a&gt;. We have 2 clauses here:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;The base case:&lt;/strong&gt; When we hit a value of &lt;code&gt;true&lt;/code&gt;, we stop looking for nested objects&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The recursive function call:&lt;/strong&gt; When we &lt;em&gt;haven&apos;t&lt;/em&gt; hit the &quot;base&quot; of our nested object, keep drilling down the chain of nested keys using the same function&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;With this in place, we have a reusable JS function for anywhere in our codebase 🥳&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; myFridge&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	weekOldPasta: {  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		noodleSogginess: “high”,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		numMeatballs: &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		isEdible: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	panSearedSalmon: {  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		oilUsed: “avocado”,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		numSpices: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		isEdible: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; whatIWant&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; graphQlQueryToJson&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  weekOldPasta {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    isEdible&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  panSearedSalmon {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    isEdible&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;).query&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;assignValuesToObjectKeys&lt;/span&gt;&lt;span&gt;(whatIWant, myFridge)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(whatIWant)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/* 👉 {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	weekOldPasta: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		isEdible: false,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	panSearedSalmon: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		isEdible: true,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Cleaning this up a little&lt;/h3&gt;&lt;p&gt;You&apos;ll notice that our &lt;code&gt;assignValuesToObjectKeys&lt;/code&gt; function doesn&apos;t return anything; it just modifies the &lt;code&gt;whatIWant&lt;/code&gt; object we passed in. For added readability, we might add a wrapper function to handle the &lt;code&gt;graphQlQueryToJson&lt;/code&gt; call and actually &lt;code&gt;return&lt;/code&gt; our requested object:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; grabPartialObject&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;originalObject&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {}, &lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; whatIWant&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; graphQlQueryToJson&lt;/span&gt;&lt;span&gt;(query).query&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	assignValuesToObjectKeys&lt;/span&gt;&lt;span&gt;(whatIWant, originalObject)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; whatIWant&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; whatsEdible&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; grabPartialObject&lt;/span&gt;&lt;span&gt;(myFridge, &lt;/span&gt;&lt;span&gt;`{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  weekOldPasta {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    isEdible&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  panSearedSalmon {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    isEdible&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(whatsEdible) &lt;/span&gt;&lt;span&gt;// gives us the same object as before!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Handling arrays&lt;/h3&gt;&lt;p&gt;So we&apos;ve conquered nested objects. But what if we have an &lt;em&gt;array&lt;/em&gt; of objects that we want to filter?&lt;/p&gt;&lt;p&gt;For instance, say our fridge data was structured just a bit differently:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; myFridge&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  food: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      name: &lt;/span&gt;&lt;span&gt;&apos;Week old pasta&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      noodleSogginess: &lt;/span&gt;&lt;span&gt;&apos;high&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      numMeatballs: &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      isEdible: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      name: &lt;/span&gt;&lt;span&gt;&apos;Pan Seared Salmon&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      oilUsed: &lt;/span&gt;&lt;span&gt;&apos;avocado&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      numSpices: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      isEdible: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...and we just care about the &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;isEdible&lt;/code&gt; keys for every object in that array. Following how GraphQL requests normally work, we&apos;d expect this sort of syntax to work:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	food&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		name&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		isEdible&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In other words, treat &lt;code&gt;food&lt;/code&gt; like it&apos;s a regular object in the request, and we&apos;ll be smart enough to handle arrays of data.&lt;/p&gt;&lt;p&gt;This answer is a bit more involved than our previous examples. So, I&apos;ll leave you with a thoroughly commented codeblock:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; assignValuesToObjectKeys&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;whatIWant&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;myFridge&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; Object.&lt;/span&gt;&lt;span&gt;keys&lt;/span&gt;&lt;span&gt;(whatIWant)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (whatIWant[key] &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	  // 👇 If the fridge data happens to be an array...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (Array.&lt;/span&gt;&lt;span&gt;isArray&lt;/span&gt;&lt;span&gt;(myFridge[key])) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // first, keep track of the object they requested&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; originalRequest&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; whatIWant[key]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // then, create an array where that request used to be&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // for us to &quot;push&quot; new elements onto&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      whatIWant[key] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // loop over the items in our array of food...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; fridgeItem&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; myFridge[key]) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // make a variable to store the result of assignValuesToObjectKeys&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // we use { ...originalRequest } here to create a &quot;copy&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; requestedItem&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;originalRequest }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // grab the keys we want out of that array element&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        assignValuesToObjectKeys&lt;/span&gt;&lt;span&gt;(requestedItem, fridgeItem)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // then, push our shiny new object onto our running list&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        whatIWant[key].&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(requestedItem)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&apos;s a fair amount of code! To briefly summarize, you&apos;ll need to:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Check when our actual data is an array, rather than a simple object&lt;/li&gt;&lt;li&gt;Loop over the actual data and &lt;code&gt;assignValuesToObjectKeys&lt;/code&gt; for each one&lt;/li&gt;&lt;li&gt;Push the results onto a running array in &lt;code&gt;whatIWant&lt;/code&gt;, with necessary helper variables to keep track of your original request&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;🚀 The finished product&lt;/h2&gt;&lt;p&gt;Here&apos;s how our finished product looks! I&apos;ve renamed &lt;code&gt;myFridge&lt;/code&gt; 👉 &lt;code&gt;actualObj&lt;/code&gt; and &lt;code&gt;whatIWant&lt;/code&gt; 👉 &lt;code&gt;requestedObj&lt;/code&gt; so our naming conventions are more universal. I also added a &lt;code&gt;hasOwnProperty&lt;/code&gt; check to assert we&apos;re requesting a key that actually exists. If not, raise an exception.&lt;/p&gt;&lt;p&gt;Remember, you&apos;ll need to add the &lt;a href=&quot;https://www.npmjs.com/package/graphql-query-to-json&quot;&gt;graphql-query-to-json package&lt;/a&gt; package to your project for this to work.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;graphQlQueryToJson&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;graphql-query-to-json&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; assignValuesToObjectKeys&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;requestedObj&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;actualObj&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; Object.&lt;/span&gt;&lt;span&gt;keys&lt;/span&gt;&lt;span&gt;(requestedObj)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;actualObj.&lt;/span&gt;&lt;span&gt;hasOwnProperty&lt;/span&gt;&lt;span&gt;(key)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		throw&lt;/span&gt;&lt;span&gt; `You requested a key that doesn&apos;t exist: ${&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (requestedObj[key] &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      requestedObj[key] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; actualObj[key]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (Array.&lt;/span&gt;&lt;span&gt;isArray&lt;/span&gt;&lt;span&gt;(actualObj[key])) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // keep track of the object they requested&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; originalRequest&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; requestedObj[key]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // then, create an array where that request used to be&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // for us to &quot;push&quot; new elements onto&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      requestedObj[key] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; actualItem&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; actualObj[key]) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // make a variable to store the result of assignValuesToObjectKeys&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // we use { ...originalRequest } here to create a &quot;copy&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; requestedItem&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;originalRequest }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        assignValuesToObjectKeys&lt;/span&gt;&lt;span&gt;(requestedItem, actualItem)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        requestedObj[key].&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(requestedItem)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(requestedObj[key])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // if the value isn&apos;t &quot;true&quot;, we must have found a nested object&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // so, we&apos;ll call this same function again, now starting from&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // the nested object inside requestedObj&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      assignValuesToObjectKeys&lt;/span&gt;&lt;span&gt;(requestedObj[key], actualObj[key])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 👇 Function you&apos;ll actually `export` for others to use&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; grabPartialObject&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;actualObj&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {}, &lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; requestedObj&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; graphQlQueryToJson&lt;/span&gt;&lt;span&gt;(query).query&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  assignValuesToObjectKeys&lt;/span&gt;&lt;span&gt;(requestedObj, actualObj)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; requestedObj&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Usage example&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;grabPartialObject&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;./some/helper/file&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; myFridge&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	weekOldPasta: {  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		noodleSogginess: “high”,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		numMeatballs: &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		isEdible: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	panSearedSalmon: {  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		oilUsed: “avocado”,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		numSpices: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		isEdible: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; whatsEdible&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; grabPartialObject&lt;/span&gt;&lt;span&gt;(myFridge, &lt;/span&gt;&lt;span&gt;`{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  weekOldPasta {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    isEdible&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  panSearedSalmon {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    isEdible&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(whatsEdible)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/* 👉 {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	weekOldPasta: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		isEdible: false,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	panSearedSalmon: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		isEdible: true,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/article&gt;</content:encoded></item><item><title>Sexy, accessible show-hide animations in any web framework</title><link>https://bholmes.dev/blog/accessible-show-hide-animations/</link><guid isPermaLink="true">https://bholmes.dev/blog/accessible-show-hide-animations/</guid><description>It&apos;s easy to build fancy fade transitions, but how do we include keyboard-ists and screenreaders in the mix?</description><pubDate>Mon, 24 May 2021 20:01:36 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;Using JS to show and hide something is a web dev rite of passage. Heck, it&apos;s the first demo that comes to mind when you think about &quot;state management&quot; (just behind building a counter 😆).&lt;/p&gt;&lt;p&gt;But when you do the classic &quot;hide whenever X variable is &lt;code&gt;false&lt;/code&gt;,&quot; there&apos;s some accessibility concerns you might be forgetting:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;can keyboard users still access that hidden element?&lt;/li&gt;&lt;li&gt;do screen readers actually &lt;em&gt;know&lt;/em&gt; what your button is trying to show and hide?&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Don&apos;t worry, I have the answers for you! Let&apos;s explore a universal solution you can bring to React, Svelte, Vue, etc, along with some debugging tips using a real screen reader 🚀&lt;/p&gt;&lt;h2&gt;Starting with a naive solution&lt;/h2&gt;&lt;p&gt;Okay, wait, why isn&apos;t this post a 2 minute read? I just whipped up this fade transition in a CodePen and it works great!&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/XWMKqWj&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;p&gt;&lt;em&gt;If you don&apos;t recognize this meme, &lt;a href=&quot;https://knowyourmeme.com/memes/nileseyy-niles-disappears&quot;&gt;go culture yourself&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;I&apos;ll admit that a simple &lt;code&gt;opacity&lt;/code&gt; shift works pretty well for non-interactable elements like this &lt;code&gt;img&lt;/code&gt;. But what if you&apos;re &lt;strong&gt;showing and hiding clickable elements,&lt;/strong&gt; like a navigation bar of links?&lt;/p&gt;&lt;p&gt;This can spell problems for those using keyboard navigation or screenreaders. It&apos;s (embarrassingly) a problem across some of my own sites that I&apos;m working to fix, but it can be tricky to debug.&lt;/p&gt;&lt;p&gt;Here&apos;s an example of hitting the &lt;code&gt;tab&lt;/code&gt; key through our &lt;a href=&quot;https://hack4impact.org/&quot;&gt;Hack4Impact.org&lt;/a&gt; site. I&apos;ve adjusted the dropdown overlay to &lt;code&gt;opacity: 0.1&lt;/code&gt; to show which items are focused, but you can expect the same behavior for &lt;code&gt;opacity: 0&lt;/code&gt;:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fshow-hide-tab-focus-demo.1V_ETTdS.gif&quot; alt=&quot;When hitting toggling the hamburger menu off on the Hack4Impact site, we see a faint focus ring over each link as we tab to these low opacity elements&quot; width=&quot;600&quot; height=&quot;445&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Follow either the focus ring around the elements, or the &lt;code&gt;activeElement&lt;/code&gt; query in the console view (which prints the element being focused)&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Uh oh, I&apos;m still able to focus and select these links! What&apos;s more, our &lt;code&gt;opacity&lt;/code&gt; solution poses a problem for visually impaired users, since they won&apos;t know that our visual &lt;code&gt;opacity&lt;/code&gt; shift &lt;em&gt;really&lt;/em&gt; means showing and hiding a dropdown.&lt;/p&gt;&lt;p&gt;What we really want is the best of &lt;em&gt;all&lt;/em&gt; worlds:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;A nifty fade in-and-out animation for sighted users&lt;/li&gt;&lt;li&gt;The ability to focus interactable links and buttons &lt;em&gt;only&lt;/em&gt; when the element is revealed&lt;/li&gt;&lt;li&gt;A callout for screenreaders whenever we show and hide something&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;⌨️ Let&apos;s accommodate keyboard users&lt;/h2&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; This section only applies when showing / hiding interactable elements like a list of links.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Focusing on that &lt;code&gt;tab&lt;/code&gt; key first, we need to find a way to make sure that a not-so-visible element can&apos;t be accessed at all.&lt;/p&gt;&lt;p&gt;You might remember a nifty property for this already: &lt;code&gt;display: none&lt;/code&gt;. This makes element nonexistent in the document, right?&lt;/p&gt;&lt;p&gt;Well it&apos;s worth a shot! Let&apos;s toggle to &lt;code&gt;display: none&lt;/code&gt; when our element is hidden, and add &lt;code&gt;display&lt;/code&gt; alongside &lt;code&gt;opacity&lt;/code&gt; in our list of transitions.&lt;/p&gt;&lt;p&gt;We&apos;ll be using this basic navigation dropdown as fuel for the rest of our code examples. Nothing fancy on the JS + HTML sides! The important piece are those &lt;code&gt;.dropdown&lt;/code&gt; and &lt;code&gt;.dropdown.expanded&lt;/code&gt; selectors in our CSS. That&apos;s where we toggle between our &lt;code&gt;display&lt;/code&gt; states like so:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.dropdown&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.dropdown.expanded&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	/* since we&apos;re using flexbox in our dropdown */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;flex&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	/* to (hopefully) fade from expanded to collapsed in 0.2 seconds */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	transition&lt;/span&gt;&lt;span&gt;: opacity &lt;/span&gt;&lt;span&gt;0.2&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;, display: &lt;/span&gt;&lt;span&gt;0.2&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Full example:&lt;/strong&gt;&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/BaWzGpY&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;p&gt;But wait, where&apos;s our fade transition? Sorry to say this is the big limitation of the &lt;code&gt;display&lt;/code&gt; property: &lt;strong&gt;it cannot be used in CSS transitions&lt;/strong&gt; ( &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties&quot;&gt;full list of animatable CSS properties here&lt;/a&gt;) This means, when we toggle off our &lt;code&gt;expanded&lt;/code&gt; class, it immediately switches to &lt;code&gt;display: none&lt;/code&gt; (ignoring our &lt;code&gt;0.2s&lt;/code&gt; transition) before our opacity has time to sexily fade out 😥&lt;/p&gt;&lt;p&gt;What we need is a way to toggle off &lt;code&gt;display&lt;/code&gt; &lt;em&gt;only after&lt;/em&gt; our element fades out of view. And no, we can&apos;t use &lt;code&gt;keyframes&lt;/code&gt; to set up this sequence. But fear not! There&apos;s a neat property called &lt;code&gt;visibility&lt;/code&gt; that can do exactly what we want. Just hot swap &lt;code&gt;display: none&lt;/code&gt; for &lt;code&gt;visibility: hidden&lt;/code&gt; and update the transition:&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/xxqOQrw&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;p&gt;And &lt;em&gt;BAM!&lt;/em&gt; Our fade animation remains in-tact, but we&apos;ve successfully hidden our element from keyboard-ists 👍&lt;/p&gt;&lt;p&gt;You can click on that CodePen preview above and start hitting &lt;code&gt;tab&lt;/code&gt; to test our solution. Your focus should immediately jump from the hamburger button to the CodePen zoom controls, instead of trying to focus those invisible links. And once you focus the button, hit &lt;code&gt;space&lt;/code&gt;, and start &lt;code&gt;tab&lt;/code&gt;ing through, those links become selectable.&lt;/p&gt;&lt;p&gt;💡 &lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; There is 1 subtle difference between &lt;code&gt;display: none&lt;/code&gt; and &lt;code&gt;visibility: hidden&lt;/code&gt;. When using the former, an element gets completely removed from the document, so any elements next to it will &lt;strong&gt;shift to take up the space it left behind.&lt;/strong&gt; By contrast, &lt;code&gt;visibility: hidden&lt;/code&gt; just hides the element visually, while &lt;strong&gt;leaving the gap&lt;/strong&gt; for where it used to sit. This prevents your website layout from shifting around when you toggle elements on and off.&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;👁 Let&apos;s accommodate screen readers&lt;/h2&gt;&lt;p&gt;Okay, so we know what our website looks like... but what does it sound like? 🤔&lt;/p&gt;&lt;p&gt;Well, I&apos;d recommend breaking out a screen reader for this next part! You can reach for the VoiceOver tool if you&apos;re running MacOS, but there&apos;s also a &lt;a href=&quot;https://chrome.google.com/webstore/detail/screen-reader/kgejglhpjiefppelpmljglcjbhoiplfn&quot;&gt;screen reader for Chrome&lt;/a&gt; you can try on any computer.&lt;/p&gt;&lt;p&gt;If you&apos;re interested, you can follow along with this video walkthrough to see the &lt;a href=&quot;https://support.apple.com/guide/voiceover-guide/welcome/web&quot;&gt;VoiceOver tool&lt;/a&gt; in action 👇&lt;/p&gt;&lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/21S1PFBxV6g&quot; width=&quot;560&quot; height=&quot;315&quot; allowfullscreen&gt;  &lt;/iframe&gt;  &lt;h3&gt;Communicating toggle states with &lt;code&gt;aria-expanded&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Our navigation toggle button is the first issue to address. If you try expanding and collapsing our menu as-is, the screenreader won&apos;t read anything &lt;em&gt;new&lt;/em&gt; to the user. This is because, well, it&apos;s a plain old button at the moment. We need to tell our reader that it&apos;s &lt;em&gt;actually&lt;/em&gt; an on-off toggle.&lt;/p&gt;&lt;p&gt;We can pull this off by adding an &lt;code&gt;aria-expanded&lt;/code&gt; attribute to our button. This boolean should call out whether our menu is in the &quot;expanded&quot; or &quot;collapsed&quot; state. Just set the initial state in our HTML:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;!-- collapsed by default --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; aria-expanded&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;false&quot;&lt;/span&gt;&lt;span&gt; aria-label&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Nav toggle&quot;&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;toggle-dropdown&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And keep that value up-to-date wherever we&apos;re tracking button clicks. Here&apos;s a basic version for plain-JS:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; toggle&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.toggle-dropdown&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;toggle.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;click&quot;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // toggle our CSS class&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  dropdown.classList.&lt;/span&gt;&lt;span&gt;toggle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;expanded&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // and if our &quot;expanded&quot; class is turned on...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (dropdown.classList.&lt;/span&gt;&lt;span&gt;contains&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;expanded&quot;&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	// set aria-expanded to true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    toggle.&lt;/span&gt;&lt;span&gt;setAttribute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;aria-expanded&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	// otherwise it&apos;s false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    toggle.&lt;/span&gt;&lt;span&gt;setAttribute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;aria-expanded&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;false&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/RwpVqdb&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Faria-expanded-screenreader-demo.CRnHlOwz.gif&quot; alt=&quot;VoiceOver output as we toggle our button. Notice we get a new announcement for every click saying either &amp;quot;expanded&amp;quot; or &amp;quot;collapsed&amp;quot;&quot; width=&quot;600&quot; height=&quot;411&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;VoiceOver output as we toggle our button. Notice we get a new announcement for every click saying either &quot;expanded&quot; or &quot;collapsed&quot;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;You can also add the attribute &lt;code&gt;aria-haspopup=&quot;true&quot;&lt;/code&gt; to your button. This will just added the word &quot;popup&quot; to the screenreader output to make behavior just a bit more understandable. Optional, but recommended!&lt;/p&gt;&lt;h3&gt;Focusing our dropdown on expanded&lt;/h3&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; This should only be used when showing / hiding interactable elements. If you&apos;re just revealing images or paragraphs, the &lt;code&gt;aria-expanded&lt;/code&gt; piece should be sufficient!&lt;/em&gt;&lt;/p&gt;&lt;p&gt;So now we&apos;re telling the user that they&apos;re expanding and collapsing something. But what is this &lt;em&gt;something&lt;/em&gt; their actually toggling?&lt;/p&gt;&lt;p&gt;Sadly, there&apos;s no straightforward way to tell the user &quot;you just expanded my fancy dropdown!&quot; There &lt;em&gt;is&lt;/em&gt; an &lt;code&gt;aria-controls&lt;/code&gt; attribute you can try, but it&apos;s only supported by the premium &lt;a href=&quot;https://www.freedomscientific.com/products/software/jaws/&quot;&gt;JAWS screen reader&lt;/a&gt; (which many users don&apos;t have access to).&lt;/p&gt;&lt;p&gt;So, we&apos;re going to offer the next best thing: just focus the first element in our dropdown to read out the contents immediately. This means shifting keyboard focus (yes, visually impaired users tend to navigate navigate using a keyboard) from our menu button to our first link whenever &lt;code&gt;aria-expanded=&quot;true&quot;&lt;/code&gt;. Of course, this only applies when revealing interactable elements like buttons or links, so it should&lt;/p&gt;&lt;p&gt;Should be simple enough! Just add a call to &lt;code&gt;focus()&lt;/code&gt; whenever we expand the element, right?&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;toggle.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;click&quot;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (dropdown.classList.&lt;/span&gt;&lt;span&gt;contains&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;expanded&quot;&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	// use firstElementChild to grab the first link&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	dropdown.firstElementChild.&lt;/span&gt;&lt;span&gt;focus&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    toggle.&lt;/span&gt;&lt;span&gt;setAttribute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;aria-expanded&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Well, this doesn&apos;t work super reliably when we have our fancy CSS transition. There&apos;s a chance we&apos;ll try to focus the first link while &lt;code&gt;visibility&lt;/code&gt; is still &lt;code&gt;hidden&lt;/code&gt;, which prevents our &lt;code&gt;focus()&lt;/code&gt; call from going through 😕&lt;/p&gt;&lt;p&gt;Luckily, there&apos;s an easy way to &quot;wait&quot; on our CSS transition to finish before setting focus. We just need to listen for the &lt;code&gt;transitionend&lt;/code&gt; event on our dropdown like so:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;dropdown.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;transitionend&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (dropdown.classList.&lt;/span&gt;&lt;span&gt;contains&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;expanded&quot;&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	// instead of calling focus() in our &apos;click&apos; listener,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	// we&apos;ll call it from here!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dropdown.firstElementChild.&lt;/span&gt;&lt;span&gt;focus&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this in place, we should get reliable link focusing with a matching screen reader output 👍&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fshow-hide-focus-first-link.CsnbjdtG.gif&quot; alt=&quot;VoiceOver output from hitting the toggle button. We see &amp;quot;Nav toggle, menu pop up collapsed, button&amp;quot; when expanded is false, and &amp;quot;Nav toggle, menu pop up expanded, button&amp;quot; when expanded is true&quot; width=&quot;600&quot; height=&quot;411&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Finished product&lt;/strong&gt;&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/vYxKVgB&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;h2&gt;⚙️ Applying this to your favorite framework&lt;/h2&gt;&lt;p&gt;We&apos;ve covered a lot of ground using plain JS, but it&apos;s quick-and-easy to apply to any component framework!&lt;/p&gt;&lt;p&gt;We just need a couple pieces:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;an &lt;code&gt;expanded&lt;/code&gt; state variable&lt;/strong&gt; to keep track of showing / hiding&lt;/li&gt;&lt;li&gt;&lt;strong&gt;a callback to &lt;code&gt;focus()&lt;/code&gt; our first dropdown element on &lt;code&gt;transitionend&lt;/code&gt;.&lt;/strong&gt; For most frameworks, we can just attach a callback function to an element&apos;s &lt;code&gt;onTransitionEnd&lt;/code&gt; attribute.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;A simple React solution&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { useState } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; DropdownExample&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;expanded&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setExpanded&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; focusFirstDropdownLink&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ({ &lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt; }) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	// if we apply this function to our dropdown,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	// the &quot;target&quot; should be a reference to the dropdown itself!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    target.firstElementChild.&lt;/span&gt;&lt;span&gt;focus&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;nav&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;toggle-dropdown&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          aria-expanded&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{expanded}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          aria-haspopup&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          aria-label&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Nav toggle&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setExpanded&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;expanded)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;nav&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		{&lt;/span&gt;&lt;span&gt;/*toggle our &quot;expanded&quot; CSS class*/&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{expanded &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &quot;dropdown expanded&quot;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &quot;dropdown&quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		{&lt;/span&gt;&lt;span&gt;/*trigger our focus() once the dropdown&apos;s fade effect finishes*/&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        onTransitionEnd&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{focusFirstDropdownLink}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;Home&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/about&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;About&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/projects&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;Projects&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/contact&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;Contact&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pretty straightforward rewrite! The ability to bind our &lt;code&gt;focus()&lt;/code&gt; function to the &lt;code&gt;onTransitionEnd&lt;/code&gt; listener saves us a lot of work. We don&apos;t even need a &lt;code&gt;useRef&lt;/code&gt; to grab the element, in fact.&lt;/p&gt;&lt;p&gt;Here&apos;s a &lt;a href=&quot;https://codesandbox.io/s/accessible-show-hide-react-4rv7m?file=/src/App.js&quot;&gt;&lt;strong&gt;working sandbox demo&lt;/strong&gt;&lt;/a&gt; for you to try 🚀&lt;/p&gt;&lt;h3&gt;Svelte example&lt;/h3&gt;&lt;p&gt;This should be equally trivial in Svelte:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	let&lt;/span&gt;&lt;span&gt; expanded &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; focusFirstDropdownLink&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ({ &lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt; }) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	    // again, &quot;target&quot; should be a reference to our dropdown&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		target.firstElementChild.&lt;/span&gt;&lt;span&gt;focus&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;nav&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; class=&quot;&lt;/span&gt;&lt;span&gt;toggle-dropdown&lt;/span&gt;&lt;span&gt;&quot; &lt;/span&gt;&lt;span&gt;aria-expanded&lt;/span&gt;&lt;span&gt;={&lt;/span&gt;&lt;span&gt;expanded&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;aria-haspopup&lt;/span&gt;&lt;span&gt;=&quot;true&quot; &lt;/span&gt;&lt;span&gt;aria-label&lt;/span&gt;&lt;span&gt;=&quot;&lt;/span&gt;&lt;span&gt;Nav&lt;/span&gt;&lt;span&gt; toggle&quot; on:click={() =&amp;gt; &lt;/span&gt;&lt;span&gt;expanded&lt;/span&gt;&lt;span&gt; = !&lt;/span&gt;&lt;span&gt;expanded&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt; viewBox=&quot;0 0 448 512&quot; width=&quot;100&quot; title=&quot;bars&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt; d=&quot;M16 132h416c8&lt;/span&gt;&lt;span&gt;.837&lt;/span&gt;&lt;span&gt; 0 16-7&lt;/span&gt;&lt;span&gt;.163&lt;/span&gt;&lt;span&gt; 16-16V76c0-8&lt;/span&gt;&lt;span&gt;.837-7.163-16-16-16H16C7.163&lt;/span&gt;&lt;span&gt; 60 0 67&lt;/span&gt;&lt;span&gt;.163&lt;/span&gt;&lt;span&gt; 0 76v40c0 8&lt;/span&gt;&lt;span&gt;.837&lt;/span&gt;&lt;span&gt; 7&lt;/span&gt;&lt;span&gt;.163&lt;/span&gt;&lt;span&gt; 16 16 16zm0 160h416c8&lt;/span&gt;&lt;span&gt;.837&lt;/span&gt;&lt;span&gt; 0 16-7&lt;/span&gt;&lt;span&gt;.163&lt;/span&gt;&lt;span&gt; 16-16v-40c0-8&lt;/span&gt;&lt;span&gt;.837-7.163-16-16-16H16c-8.837&lt;/span&gt;&lt;span&gt; 0-16 7&lt;/span&gt;&lt;span&gt;.163-16&lt;/span&gt;&lt;span&gt; 16v40c0 8&lt;/span&gt;&lt;span&gt;.837&lt;/span&gt;&lt;span&gt; 7&lt;/span&gt;&lt;span&gt;.163&lt;/span&gt;&lt;span&gt; 16 16 16zm0 160h416c8&lt;/span&gt;&lt;span&gt;.837&lt;/span&gt;&lt;span&gt; 0 16-7&lt;/span&gt;&lt;span&gt;.163&lt;/span&gt;&lt;span&gt; 16-16v-40c0-8&lt;/span&gt;&lt;span&gt;.837-7.163-16-16-16H16c-8.837&lt;/span&gt;&lt;span&gt; 0-16 7&lt;/span&gt;&lt;span&gt;.163-16&lt;/span&gt;&lt;span&gt; 16v40c0 8&lt;/span&gt;&lt;span&gt;.837&lt;/span&gt;&lt;span&gt; 7&lt;/span&gt;&lt;span&gt;.163&lt;/span&gt;&lt;span&gt; 16 16 16z&quot; /&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;nav&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!--Note we can &lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt; class:expanded to bind our &quot;expanded&quot; CSS class to the &quot;expanded&quot; state &lt;/span&gt;&lt;span&gt;variable--&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class=&quot;dropdown&quot; class:expanded on:transitionend={&lt;/span&gt;&lt;span&gt;focusFirstDropdownLink&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href=&quot;/&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;Home&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href=&quot;/about&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;About&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href=&quot;/projects&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;Projects&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href=&quot;/contact&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;Contact&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here&apos;s another &lt;a href=&quot;https://svelte.dev/repl/daa482983cd84a199f34895cdd3a08f6?version=3.38.2&quot;&gt;&lt;strong&gt;working sandbox demo&lt;/strong&gt;&lt;/a&gt; for you to try 🚀&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Want CSS variables in media query declarations? Try this!</title><link>https://bholmes.dev/blog/alternative-to-css-variable-media-queries/</link><guid isPermaLink="true">https://bholmes.dev/blog/alternative-to-css-variable-media-queries/</guid><description>Let&apos;s see why CSS variables / custom properties fail in media queries + some workarounds to try</description><pubDate>Wed, 12 May 2021 14:01:23 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;If you&apos;re like me, you&apos;ve probably been &lt;a href=&quot;https://bholmes.dev/blog/how-using-css-variables-cut-down-on-my-javascript/&quot;&gt;stretching CSS variables / custom properties to their limits&lt;/a&gt; while building your own design systems. But this &quot;silver bullet&quot; can lead to a nasty roadblock: &lt;strong&gt;you can&apos;t use them in media query declarations.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;To clarify, this is the behavior you might want:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;:root&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	--mobile-breakpoint&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	--tablet-breakpoint&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;900&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;max-width&lt;/span&gt;&lt;span&gt;: var(--mobile-breakpoint)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.obligatory-hamburger-menu&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;visibility&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;visible&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...which sadly won&apos;t work.&lt;/p&gt;&lt;h2&gt;Why var() can&apos;t work in a @media declaration&lt;/h2&gt;&lt;p&gt;This may seem confusing at first. If our variables are declared at the &lt;code&gt;:root&lt;/code&gt; of the page, why can&apos;t a media query access them?&lt;/p&gt;&lt;p&gt;Well, it comes down to what the &lt;code&gt;:root&lt;/code&gt; selector actually means: the root &lt;em&gt;element&lt;/em&gt; of the HTML document. Unlike CSS selectors, media queries aren&apos;t attached to HTML elements at all; they belong to the global scope.&lt;/p&gt;&lt;p&gt;If this seems confusing, let&apos;s consider a CSS variable like this one on the &lt;code&gt;body&lt;/code&gt; element:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	--mobile-breakpoint&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;max-width&lt;/span&gt;&lt;span&gt;: var(--mobile-breakpoint)) { &lt;/span&gt;&lt;span&gt;/* ... */&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since our &lt;code&gt;@media&lt;/code&gt; query is global, it cannot know about this &lt;code&gt;--mobile-breakpoint&lt;/code&gt; variable. This extends to the &lt;code&gt;:root&lt;/code&gt; selector as well.&lt;/p&gt;&lt;p&gt;SASS / SCSS can make this even more confusing by allowing &lt;code&gt;@media&lt;/code&gt; blocks to be nested inside of other rulesets. Don&apos;t be fooled! When your SASS gets compiled, those media queries will be hoisted to the base of your CSS document.&lt;/p&gt;&lt;h2&gt;For SASS and SCSS users: use SASS variables for media queries&lt;/h2&gt;&lt;p&gt;If you&apos;re using SASS or SCSS, I recommend falling back to SASS variables for media queries and using CSS variables everywhere else.&lt;/p&gt;&lt;p&gt;This is because, unlike CSS variables, SASS variables are replaced at build-time with the values you set. This allows the SASS compiler to find-and-replace all instances of a variable, including variables referenced by &lt;code&gt;@media&lt;/code&gt; query declarations.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;min-width&lt;/span&gt;&lt;span&gt;: $mobile-breakpoint) { &lt;/span&gt;&lt;span&gt;/* ... */&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/* compiled output: */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;min-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;/* ... */&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Build-time replacement means you won&apos;t be able to &lt;em&gt;dynamically&lt;/em&gt; update the value of a SASS variable from JavaScript. Still, you can use SASS variables to define static values in a design system like a &lt;code&gt;$mobile-breakpoint&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Note that you&apos;ll need to fall back to Sass for math calculations as well, instead of using the dynamic &lt;code&gt;calc()&lt;/code&gt; function:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* ✅ good */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;max-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;$mobile-breakpoint&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 50&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;/* ... */&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/* ❌ bad */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;max-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;calc&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$mobile-breakpoint&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 50&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;)) { &lt;/span&gt;&lt;span&gt;/* ... */&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;For everyone else: use the incoming CSS environment variables spec&lt;/h2&gt;&lt;p&gt;The W3C community isn&apos;t happy about the limitations of CSS variables either. &lt;a href=&quot;https://drafts.csswg.org/css-env-1/&quot;&gt;Their proposal for environment variables&lt;/a&gt; addresses this very issue, letting you declare static variables that can be read from &lt;code&gt;@media&lt;/code&gt; queries.&lt;/p&gt;&lt;p&gt;Browsers have followed &lt;a href=&quot;https://drafts.csswg.org/css-env-1/&quot;&gt;this level 1 spec&lt;/a&gt; to let you read environment variables via the &lt;code&gt;env()&lt;/code&gt; function. Similar to &lt;code&gt;var()&lt;/code&gt;, you can call &lt;code&gt;env()&lt;/code&gt; to read a variable&apos;s value like so:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;min-width&lt;/span&gt;&lt;span&gt;: env(...)) { &lt;/span&gt;&lt;span&gt;/* ... */&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Before you go off and use it, there&apos;s a surprising show-stopper: you can&apos;t &lt;strong&gt;declare&lt;/strong&gt; variables of your own yet 😅 Browser engines like webkit include a set of &lt;em&gt;predefined&lt;/em&gt; environment variables for a different use case: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/env#syntax&quot;&gt;setting the allowed area for notification popups&lt;/a&gt; when building web apps. Useful, but it doesn&apos;t address our &lt;code&gt;@media&lt;/code&gt; query dilemma.&lt;/p&gt;&lt;h3&gt;PostCSS polyfill for declaring environment variables&lt;/h3&gt;&lt;p&gt;Luckily, &lt;a href=&quot;https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-env-function&quot;&gt;there&apos;s a PostCSS plugin&lt;/a&gt; to let you define custom environment variables with a similar syntax to CSS variables. Install the plugin with &lt;code&gt;npm&lt;/code&gt; like so:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; postcss&lt;/span&gt;&lt;span&gt; postcss-env-function&lt;/span&gt;&lt;span&gt; --save-dev&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And define variables from your PostCSS configuration using the &lt;code&gt;environmentVariables&lt;/code&gt; object:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;postcssEnvFunction&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    environmentVariables: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &apos;--mobile-breakpoint&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;600px&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, you can refer to variables from &lt;code&gt;@media&lt;/code&gt; breakpoints using the &lt;code&gt;env()&lt;/code&gt; function:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;min-width&lt;/span&gt;&lt;span&gt;: env(--mobile-breakpoint)) { &lt;/span&gt;&lt;span&gt;/* ... */&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This has the same limitation as SASS variables: values are set at build-time and can&apos;t be updated dynamically. That also means dynamic functions like &lt;code&gt;calc()&lt;/code&gt; won&apos;t work in &lt;code&gt;@media&lt;/code&gt; queries. Still, this is a helpful workaround to reference environment variables using the familiar CSS variable syntax.&lt;/p&gt;&lt;p&gt;⚠️ &lt;strong&gt;Word of caution:&lt;/strong&gt; This PostCSS plugin is a &quot;stage 0&quot; prototype, and it may not match browser behavior if environment variable declarations come to native CSS. Be sure to document your usage of this plugin and keep an eye on the spec for updates.&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Picture perfect image optimization for any web framework</title><link>https://bholmes.dev/blog/picture-perfect-image-optimization/</link><guid isPermaLink="true">https://bholmes.dev/blog/picture-perfect-image-optimization/</guid><description>Image optimization is never fun to deal with. Let&apos;s learn how the picture element + 11ty can save any static site from bloated load times.</description><pubDate>Tue, 04 May 2021 20:01:36 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;If you&apos;ve been building websites for a while, &quot;optimize your images&quot; probably sounds like &quot;eat your veggies.&quot; It&apos;s good for your website&apos;s health, it&apos;ll make your SEO big and strong... but compressing every image by hand doesn&apos;t sound appetizing to me 🤢&lt;/p&gt;&lt;p&gt;So we&apos;re going to talk &lt;strong&gt;easy wins&lt;/strong&gt; for 1) optimizing image file formats and sizes with the &lt;code&gt;picture&lt;/code&gt; element, and 2) an automation process using 11ty that you can take with you to your build setup of choice 💪&lt;/p&gt;&lt;p&gt;💁 &lt;strong&gt;Intended audience:&lt;/strong&gt; &lt;em&gt;This is meant for developers building &quot;template-driven&quot;  static sites (11ty, Jekyll, Hugo, plain HTML) or &quot;component-driven&quot; web apps (NextJS, Gatsby, etc). If you&apos;re working with site builders like Wordpress or Shopify, this probably isn&apos;t the article for you!&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;🥦 So what&apos;s wrong with my images right now?&lt;/h2&gt;&lt;p&gt;To show what&apos;s at stake, here&apos;s the lighthouse rating from one of my recent blog posts (images compressed with &lt;a href=&quot;https://tinyjpg.com/&quot;&gt;tinyJPG&lt;/a&gt; mind you!)&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fimage-opt-bad-lighthouse-score.CfRQPK0T.png&quot; alt=&quot;List of poor image loadtimes from Chromium Lighthouse performance report&quot; width=&quot;1414&quot; height=&quot;1670&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Yikes!&lt;/em&gt; 10 seconds to grab all those images? Chromium definitely does some throttling to test on &quot;slower&quot; internet connections, but it&apos;s clear those KB rating are quite high (especially for mobile users).&lt;/p&gt;&lt;p&gt;This just to show that &lt;strong&gt;there&apos;s much more to image optimization than compression!&lt;/strong&gt; There&apos;s also:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Serving the right format,&lt;/strong&gt; with JPGs preferrable and &lt;code&gt;.webp&lt;/code&gt; or &lt;code&gt;.avi&lt;/code&gt; &lt;em&gt;especially&lt;/em&gt; so&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Serving the right size,&lt;/strong&gt; ideally with &lt;em&gt;multiple copies&lt;/em&gt; of the same image at different widths and heights&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Loading at the right time,&lt;/strong&gt; reaching for &quot;lazy&quot; loading when we can&lt;/li&gt;&lt;li&gt;Heck, even &lt;strong&gt;including &lt;code&gt;alt&lt;/code&gt; text&lt;/strong&gt; can affect your site from both accessibility and SEO standpoints&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I learned a bit about addressing those format and size problems using &lt;code&gt;picture&lt;/code&gt; elements, and my lighthouse definitely thanked me for it 😄&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Dang, at first my performance on &lt;a href=&quot;https://t.co/iTZUPe5aBp&quot;&gt;https://t.co/iTZUPe5aBp&lt;/a&gt; was getting destroyed by a *ton* of fat JPGs I never condensed down (especially on blog posts). Installed &lt;a href=&quot;https://x.com/eleven_ty?ref_src=twsrc%5Etfw&quot;&gt;@eleven_ty&lt;/a&gt; &apos;s image plugin, put in an hour of work... and just look at it 🤩 &lt;a href=&quot;https://t.co/l5GLACk8F7&quot;&gt;pic.twitter.com/l5GLACk8F7&lt;/a&gt;&lt;/p&gt;— Ben Holmes (@BHolmesDev) &lt;a href=&quot;https://x.com/BHolmesDev/status/1378869249930842116?ref_src=twsrc%5Etfw&quot;&gt;April 5, 2021&lt;/a&gt;&lt;/blockquote&gt;

 &lt;h2&gt;🌅 Fixing format + size problems with the &lt;code&gt;picture&lt;/code&gt; element&lt;/h2&gt;&lt;p&gt;So how can we deliver different image files for the right people? Well, let&apos;s start with a humble image element like this one:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/assets/mega-chonker.jpg&quot;&lt;/span&gt;&lt;span&gt; width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;1000&quot;&lt;/span&gt;&lt;span&gt; height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;600&quot;&lt;/span&gt;&lt;span&gt; alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;A perfectly sized cat&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&amp;lt;details&amp;gt;&amp;lt;summary&amp;gt;Chonk level chart&amp;lt;/summary&amp;gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fheckin-chonker.BLe8hU2h.jpg&quot; alt=&quot;Chart of chonk levels for cats&quot; width=&quot;5550&quot; height=&quot;1816&quot; /&gt;&lt;/p&gt;&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;&lt;p&gt;Now, say we&apos;ve opened our image editor and saved a smaller version for mobile users sitting at, say, 600 pixels wide. You could probably set up some CSS to hot-swap your images depending on your screen width:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;desktop&quot;&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/assets/chonker-1000w.jpg&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;1000&quot;&lt;/span&gt;&lt;span&gt; height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;600&quot;&lt;/span&gt;&lt;span&gt; alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;A perfectly sized cat&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;mobile&quot;&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/assets/chonker-600w.jpg&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;600&quot;&lt;/span&gt;&lt;span&gt; height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;300&quot;&lt;/span&gt;&lt;span&gt; alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;A perfectly sized cat&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;max-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .desktop&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;min-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;601&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .mobile&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...But this isn&apos;t very scalable. What if we&apos;re, say, working in a markdown file where we can&apos;t append class names? Or we have different formats we want to switch between depending on browser support (JPEG vs WEBP for example)?&lt;/p&gt;&lt;p&gt;This is where the &lt;code&gt;picture&lt;/code&gt; element comes in. Take this example here:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;picture&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;!-- List out all the WEBP images + WEBP sizes we can choose from --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;image/webp&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    srcset&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/assets/chonker-600w.webp 600w, /assets/chonker-1000w.webp 1000w&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    sizes&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;100vw&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;!-- In case a browser doesn&apos;t support WEBP, fall back to this set of JPG sources --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;image/jpeg&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    srcset&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/assets/chonker-600w.jpg 600w, /assets/chonker-1000w.jpg 1000w&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    sizes&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;100vw&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;!-- The actual, style-able img element that &quot;receives&quot; these sources --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;!-- Also includes a default src in case no &amp;lt;source&amp;gt; can be applied --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/assets/chonker-600.png&quot;&lt;/span&gt;&lt;span&gt; alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;A perfectly sized cat&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;picture&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Some big takeaways:&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;We can wrap our image tags in a &lt;code&gt;picture&lt;/code&gt; to unlock a &quot;switch&quot; case of sorts, with your browser picking the first &lt;code&gt;source&lt;/code&gt; element that it&apos;s able to render. But admittedly, &lt;em&gt;most&lt;/em&gt; modern browsers will reach for those shiny &lt;code&gt;.webp&lt;/code&gt; files listed under &lt;code&gt;type=&quot;image/webp&quot;&lt;/code&gt; without needing the JPG fallbacks (&lt;a href=&quot;https://caniuse.com/?search=webp&quot;&gt;current browser support here&lt;/a&gt;).&lt;/li&gt;&lt;li&gt;Each source has a &lt;strong&gt;&lt;code&gt;srcset&lt;/code&gt; property&lt;/strong&gt;, which takes in a list of source URLs for a given image format. These sources are comma-separated, plus a pixel-value width using that &lt;code&gt;w&lt;/code&gt; on the end. The browser will then decide which source to use based on the &lt;code&gt;sizes&lt;/code&gt; property (more on that in the next section)&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Picture elements are not images themselves!&lt;/strong&gt; This is an interesting gotcha when you start trying to style those images. So, you&apos;ll want to keep putting all your image-specific CSS (ex. &lt;code&gt;object-fit&lt;/code&gt;) on that &lt;code&gt;img&lt;/code&gt; element instead of the &lt;code&gt;picture&lt;/code&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;The &lt;code&gt;sizes&lt;/code&gt; attribute&lt;/h3&gt;&lt;p&gt;&lt;code&gt;Sizes&lt;/code&gt; is an interesting beast. It &lt;em&gt;almost&lt;/em&gt; looks like CSS actually, with some small syntax differences.&lt;/p&gt;&lt;p&gt;Remember those &lt;code&gt;mobile&lt;/code&gt; and &lt;code&gt;desktop&lt;/code&gt; helper classes from earlier? Well, &lt;code&gt;sizes&lt;/code&gt; let us do something rather similar.&lt;/p&gt;&lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/uGiG2VWkeSs&quot; width=&quot;560&quot; height=&quot;315&quot; allowfullscreen&gt;  &lt;/iframe&gt;  &lt;p&gt;&lt;strong&gt;Key takeaways from the video:&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In general, the &lt;code&gt;sizes&lt;/code&gt; attribute is a way to tell the browser &lt;strong&gt;which image to use for a given screen size.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Let&apos;s say we have a banner image that takes up the full width of the screen for mobile users, but we have a table of contents that takes up half the width at &lt;code&gt;500px&lt;/code&gt; wide and above.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fimage-sizes-table-of-contents-demo.lj7qQbVK.png&quot; alt=&quot;Table of contents taking up half the width of the screen&quot; width=&quot;1150&quot; height=&quot;566&quot; /&gt;&lt;/p&gt;&lt;p&gt;Putting on our CSS hat, this means our image is &lt;code&gt;100vw&lt;/code&gt; (100% screen width) below &lt;code&gt;500px&lt;/code&gt;, and &lt;code&gt;50vw&lt;/code&gt; when we hit &lt;code&gt;@media (min-width: 500px)&lt;/code&gt;. This perfectly translates to &lt;code&gt;sizes&lt;/code&gt; 👉 &lt;code&gt;sizes=&quot;(min-width: 500px) 50vw, 100vw&quot;&lt;/code&gt;&lt;/p&gt;&lt;p&gt;And in the context of a &lt;code&gt;picture&lt;/code&gt; element:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;picture&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;!--stack up your media queries as sizes, delineated by commas &quot;,&quot;--&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;image/webp&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    srcset&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/img/6dfd7ac6-600.webp 600w, /img/6dfd7ac6-900.webp 900w...&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    sizes&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;(min-width: 500px) 50vw, 100vw&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Blue and purple cluster of stars&quot;&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/img/6dfd7ac6-600.jpeg&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;picture&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As a rule-of-thumb, you should probably use &lt;code&gt;100vw&lt;/code&gt; as a &quot;base case&quot; for smaller devices, and rack up media queries on top depending on how your layout changes. This does mean &lt;code&gt;sizes&lt;/code&gt; will be different depending on the &lt;em&gt;context&lt;/em&gt; your images are living in, so look out for that if you&apos;re using a component-based framework!&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; You may be wondering why browsers can&apos;t do all this work for us. Well, this comes down to the unpredictable nature of &quot;width&quot; when you&apos;re throwing around CSS everywhere. If you&apos;re like me, you tend to use a lot of percentages like &lt;code&gt;width: 100%&lt;/code&gt; for image blocks, which may adjust depending on the container, padding, margins, etc that get applied. If the browser tried to decipher all this styling before loading an image, you&apos;d be waiting a lot longer than you might want!&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;Respecting HD displays&lt;/h3&gt;&lt;p&gt;Note that our screen&apos;s pixel density can also affect which image gets picked from a given &lt;code&gt;srcset&lt;/code&gt;. For high-density mobile displays, it&apos;ll actually choose an image that&apos;s &lt;strong&gt;roughly double&lt;/strong&gt; the width you specify! Say we have a simple &lt;code&gt;picture&lt;/code&gt; declaration like this for instance:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;picture&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;image/webp&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    srcset&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/img/galaxy-600.webp 600w, /img/galaxy-1200.webp 1200w&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    sizes&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;100vw&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;picture&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&apos;re using &lt;code&gt;100vw&lt;/code&gt; here, so the browser should match the image source&apos;s width to the width of the display. Intuitively, we&apos;d think that a &lt;code&gt;600px&lt;/code&gt; wide display would receive &lt;code&gt;/img/galaxy-600.webp&lt;/code&gt;... but for HD displays like Macbooks or modern smartphones, it&apos;ll actually reach for an image at 600 x 2 pixels wide (&lt;code&gt;/img/galaxy-1200.webp 1200w&lt;/code&gt; in this case). So when you&apos;re generating multiple image sizes, always reach for those higher values 💡&lt;/p&gt;&lt;h2&gt;🔨 Applying this to your site with 11ty image&lt;/h2&gt;&lt;p&gt;Alright, so we see how useful the &lt;code&gt;picture&lt;/code&gt; element can be... but it&apos;s only as powerful as the pictures we can supply to it. Do we really want to create all those beautifully resized, optimized, multi-format images &lt;em&gt;by hand?&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Luckily, there&apos;s a lot of tools to handle this process for us, and I&apos;m going to hone in on the simplest I&apos;ve found: &lt;a href=&quot;https://www.11ty.dev/docs/plugins/image/#output-directory&quot;&gt;11ty&apos;s image plugin.&lt;/a&gt;&lt;/p&gt;&lt;p&gt;🚨 Now before you start scrolling to the next section, &lt;strong&gt;no, you don&apos;t need to build your site with 11ty to use this.&lt;/strong&gt; Playing around with this tool, I realized it&apos;s perfect for generating optimized images on-the-fly for &lt;em&gt;any&lt;/em&gt; use case, no command line prowess required 🔥&lt;/p&gt;&lt;h3&gt;Generating optimized images&lt;/h3&gt;&lt;p&gt;Let&apos;s play along at home! Seriously, drop everything and go open your code editor 🧑‍💻 Then, make a fresh directory / folder and create a basic &lt;code&gt;package.json&lt;/code&gt;. We&apos;ll be installing the &lt;code&gt;@11ty/eleventy-img&lt;/code&gt; dependency:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; woah-11ty-image-is-cool&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; woah-11ty-image-is-cool&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; # Make a package.json with defaults for everything&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span&gt; @11ty/eleventy-img&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now make a random JavaScript file for us to play with (I&apos;ll call mine &lt;code&gt;image-generator.js&lt;/code&gt;). Inside, just paste the example code at the top of &lt;a href=&quot;https://www.11ty.dev/docs/plugins/image/#output-directory&quot;&gt;11ty&apos;s documentation&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;@11ty/eleventy-img&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; url &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;https://images.unsplash.com/photo-1608178398319-48f814d0750c&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; stats &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt;(url, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    widths: [&lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(stats);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hm, this looks pretty straightforward. Let&apos;s run it from our terminal and see what happens:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt; ./image-generator.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With any luck, you should see a couple new faces appear:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;A &lt;code&gt;/img&lt;/code&gt; directory&lt;/strong&gt; with 2 images inside: 1 JPG picture of a galaxy that&apos;s 300 pixels wide, and a matching &lt;code&gt;webp&lt;/code&gt; image of the same size. Notice how this matches up with our &lt;code&gt;widths&lt;/code&gt; array from the code snippet 👀&lt;/li&gt;&lt;li&gt;&lt;strong&gt;A &lt;code&gt;/cache&lt;/code&gt; directory&lt;/strong&gt; with some strings of characters. Think of this like a note-to-self for the plugin about the image we downloaded. It&apos;s expensive to download images off the internet, so to avoid loading it &lt;em&gt;every time we run the script,&lt;/em&gt; 11ty checks the cache to see if we&apos;ve already loaded the image in the past 👍&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;You&apos;ll also see a &lt;a href=&quot;https://www.11ty.dev/docs/plugins/image/#usage&quot;&gt;blob of &quot;stats&quot;&lt;/a&gt; logged to your console. Most of these properties are self-explanatory, and some should look familiar from our &lt;code&gt;picture&lt;/code&gt; walkthrough earlier on (namely the &lt;code&gt;sourceType&lt;/code&gt; and &lt;code&gt;srcset&lt;/code&gt; attributes). We even get the output &lt;code&gt;size&lt;/code&gt; of the image in bytes, in case you want to inspect the differences between formats and sizes.&lt;/p&gt;&lt;p&gt;But wait, there&apos;s more! Let&apos;s try experimenting with different widths and formats:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; stats &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt;(url, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  widths: [&lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1400&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  formats: [&lt;/span&gt;&lt;span&gt;&apos;jpg&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;webp&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;gif&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We should get a plethora of resolutions inside that &lt;code&gt;img&lt;/code&gt; directory. As you can imagine, this is perfect for our picture element from earlier. You can whip up all the &lt;code&gt;source&lt;/code&gt;s and &lt;code&gt;size&lt;/code&gt; attributes by hand as a learning exercise...&lt;/p&gt;&lt;h3&gt;Automating our picture elements&lt;/h3&gt;&lt;p&gt;...Or let the plugin do this for us! Along with that handy array of &lt;code&gt;stats&lt;/code&gt;, 11ty image will splice everything into a valid &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element. All we need is a call to the &lt;code&gt;generateHTML&lt;/code&gt; helper:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;@11ty/eleventy-img&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; url &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;https://images.unsplash.com/photo-1608178398319-48f814d0750c&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; stats &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt;(url, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    widths: [&lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1400&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; html&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Image.&lt;/span&gt;&lt;span&gt;generateHTML&lt;/span&gt;&lt;span&gt;(stats, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  	alt: &lt;/span&gt;&lt;span&gt;&quot;A blue and purple galaxy of stars&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// alt text is required!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	sizes: &lt;/span&gt;&lt;span&gt;&quot;100vw&quot;&lt;/span&gt;&lt;span&gt; // remember our training with &quot;sizes&quot; from earlier...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(html);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With any luck, we should see a beautiful &lt;code&gt;picture&lt;/code&gt; we can use anywhere on our site:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;picture&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;image/webp&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          srcset&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/img/6dfd7ac6-300.webp 300w, /img/6dfd7ac6-1000.webp 1000w,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                  /img/6dfd7ac6-1400.webp 1400w&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          sizes&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;100vw&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;image/jpeg&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          srcset&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/img/6dfd7ac6-300.jpeg 300w, /img/6dfd7ac6-1000.jpeg 1000w,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                  /img/6dfd7ac6-1400.jpeg 1400w&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          sizes&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;100vw&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;A blue and purple galaxy of stars&quot;&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/img/6dfd7ac6-300.jpeg&quot;&lt;/span&gt;&lt;span&gt; width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;1400&quot;&lt;/span&gt;&lt;span&gt; height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;1402&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;picture&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Going further&lt;/h3&gt;&lt;p&gt;This plugin has a whole host of extra options to explore too, like&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www.11ty.dev/docs/plugins/image/#caching-remote-images-locally-new-in-image-0.3.0&quot;&gt;messing with cache options&lt;/a&gt; for faster build times&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.11ty.dev/docs/plugins/image/#synchronous-usage&quot;&gt;generating image stats + picture elements synchronously&lt;/a&gt;, so you don&apos;t have wait for the images to &lt;em&gt;actually&lt;/em&gt; get generated&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.11ty.dev/docs/plugins/image/#advanced-control-of-sharp-image-processor&quot;&gt;fine-tuning the Sharp image processor&lt;/a&gt; to tweak the output to your needs&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;📣 Using 11ty image with any framework&lt;/h2&gt;&lt;p&gt;If all this &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; madness excites you, go throw this 11ty image plugin at your own &lt;code&gt;/assets&lt;/code&gt; directory! I wrote this handy little script to crawl all the images in a directory (&lt;a href=&quot;https://coderrocketfuel.com/article/recursively-list-all-the-files-in-a-directory-using-node-js&quot;&gt;not recursively mind you&lt;/a&gt;) and spit out some optimized files:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;@11ty/eleventy-img&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;readdir&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;fs/promises&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;// node helper for reading folders&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;path&apos;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;// node helper for grabbing file names&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;;(&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; imageDir&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;./images&apos;&lt;/span&gt;&lt;span&gt; // match this to your assets directory&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; files&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; readdir&lt;/span&gt;&lt;span&gt;(imageDir)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; file&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; files) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; stats&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt;(imageDir &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &apos;/&apos;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; file, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      widths: [&lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1400&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;// edit to your heart&apos;s content&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      filenameFormat&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;src&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // make the filename something we can recognize.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // In this case, it&apos;s just:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // [original file name] - [image width] . [file format]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; `${&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}-${&lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;}.${&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(stats) &lt;/span&gt;&lt;span&gt;// remove this if you don&apos;t want the logs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you &lt;em&gt;happen&lt;/em&gt; to use 11ty on your personal site (or at least want to try), you can automate the &lt;code&gt;picture&lt;/code&gt; element insertion as well. &lt;a href=&quot;https://www.11ty.dev/docs/plugins/image/#use-this-in-your-templates&quot;&gt;Their guide&lt;/a&gt; covers building your own &quot;shortcode&quot; function to insert the right &lt;code&gt;picture&lt;/code&gt; for every unoptimized image on your site.&lt;/p&gt;&lt;p&gt;Even without this luxury though, this script is a great addition to any JS-based build step. Here&apos;s a basic &lt;code&gt;Image&lt;/code&gt; component I could slap into any React app based on that script above:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// consider using TypeScript for checking all these props!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; Image&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ({ &lt;/span&gt;&lt;span&gt;fileName&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;sizes&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;imageProps&lt;/span&gt;&lt;span&gt; }) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;picture&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	  &amp;lt;&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;image/webp&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		srcSet&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`/img/${&lt;/span&gt;&lt;span&gt;fileName&lt;/span&gt;&lt;span&gt;}-600.webp 600w, /img/${&lt;/span&gt;&lt;span&gt;fileName&lt;/span&gt;&lt;span&gt;}-1000.webp 1000w, /img/${&lt;/span&gt;&lt;span&gt;fileName&lt;/span&gt;&lt;span&gt;}-1400.webp 1400w`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		sizes&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{sizes}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	  /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	  &amp;lt;&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;image/jpeg&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		srcSet&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`/img/${&lt;/span&gt;&lt;span&gt;fileName&lt;/span&gt;&lt;span&gt;}-600.jpeg 600w, /img/${&lt;/span&gt;&lt;span&gt;fileName&lt;/span&gt;&lt;span&gt;}-1000.jpeg 1000w, /img/${&lt;/span&gt;&lt;span&gt;fileName&lt;/span&gt;&lt;span&gt;}-1400.jpeg 1400w`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		sizes&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{sizes}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	  /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	  &amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`/img/${&lt;/span&gt;&lt;span&gt;fileName&lt;/span&gt;&lt;span&gt;}-600.jpeg`&lt;/span&gt;&lt;span&gt;} {&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;imageProps} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;/&lt;/span&gt;&lt;span&gt;picture&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Assuming all my images get generated with this file naming convention (and I always have images at 600, 1000, and 1400 widths), this should pull all our optimized images no problem 👍&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Here&apos;s a brief rundown&lt;/strong&gt; on applying these learnings to &lt;code&gt;create-react-app&lt;/code&gt;:&lt;/p&gt;&lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/RAzXB-qu22s&quot; width=&quot;560&quot; height=&quot;315&quot; allowfullscreen&gt;  &lt;/iframe&gt;  &lt;h3&gt;Go try it yourself&lt;/h3&gt;&lt;p&gt;You can check out a running example of &lt;code&gt;create-react-app&lt;/code&gt; + 11ty image in &lt;a href=&quot;https://codesandbox.io/s/little-tree-dmr1w?file=/App.js&quot;&gt;&lt;strong&gt;this CodeSandbox&lt;/strong&gt;&lt;/a&gt; 🪄&lt;/p&gt;&lt;p&gt;This version will listen for new images during development as well. Feel free to &lt;a href=&quot;https://github.com/bholmesdev/create-react-app-with-11ty-image&quot;&gt;fork the source code&lt;/a&gt; to try in your own project (and find the edge cases I inevitably missed 😉).&lt;/p&gt;&lt;h3&gt;Other options for Next, Nuxt, Gatsby and more&lt;/h3&gt;&lt;p&gt;As cool as 11ty image can be, I should definitely highlight some &quot;native&quot; options for popular meta-frameworks:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;For Next, &lt;a href=&quot;https://nextjs.org/docs/api-reference/next/image&quot;&gt;their built-in Image component&lt;/a&gt; is perfect.&lt;/strong&gt; They&apos;ll also cover our sizes, formats, and image compression automatically, plus some neat props for eagerly loading images that are &quot;above the fold&quot; using &lt;code&gt;priority&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;For Nuxt, &lt;a href=&quot;https://image.nuxtjs.org/components/nuxt-img/&quot;&gt;their &lt;code&gt;&amp;lt;nuxt-img&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;nuxt-picture&amp;gt;&lt;/code&gt; components&lt;/a&gt; should have you covered.&lt;/strong&gt; These offer most of the same benefits as our 11ty image plugin, letting you specify different formats, a &lt;code&gt;sizes&lt;/code&gt; attribute, and background image compression. Just be sure to use &lt;code&gt;nuxt-picture&lt;/code&gt; if you want to allow multiple image formats instead of just one!&lt;/li&gt;&lt;li&gt;&lt;strong&gt;For Gatsby, &lt;a href=&quot;https://www.gatsbyjs.com/plugins/gatsby-plugin-image&quot;&gt;you&apos;ve got the gold standard of image optimization&lt;/a&gt; 🏆&lt;/strong&gt; Their image plugin was actually my main reason for using the framework a few years back, and it&apos;s only gotten better. The nicest feature (beyond matching everything we&apos;ve talked about) is their loading animations for images. You can fade in over vector traces of an image, use a blur effect, and a lot more. The only downside is the hefty JS bundle it loads into the browser to pull this off, which I&apos;ve &lt;a href=&quot;https://bholmes.dev/blog/before-building-your-next-static-site-with-react-consider-this/&quot;&gt;given my opinions on over here&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Beyond the framework, you can &lt;a href=&quot;https://davidwalsh.name/image-optimization-cloudinary&quot;&gt;optimize remotely using something like Cloudinary&lt;/a&gt;.&lt;/strong&gt; This is a great option if you don&apos;t own the build process for your website, or don&apos;t want to store your images inside your code repository. For example, you can point all your Wordpress images to a cloudinary bucket and pull different image widths and formats for there. The only downside is the cost, since Cloudinary is doing all this image processing and storage for you.&lt;/li&gt;&lt;/ul&gt;&lt;/article&gt;</content:encoded></item><item><title>How ES Modules have redefined web development</title><link>https://bholmes.dev/blog/how-es-modules-have-redefined-web-development/</link><guid isPermaLink="true">https://bholmes.dev/blog/how-es-modules-have-redefined-web-development/</guid><description>The world has been moving away from &quot;pancake&quot; bundling and towards dynamic module imports. Let&apos;s see the latest trends and what they mean for us.</description><pubDate>Wed, 31 Mar 2021 20:01:36 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;You know the inspirational phrase &quot;skate to where the puck is going?&quot; Well, in web development... it feels like the puck is teleporting across the rink at Mach 30 sometimes.&lt;/p&gt;&lt;p&gt;That&apos;s how I felt diving into how ES Modules work. Turns out, there&apos;s been some huge shifts right under my framework-laden nose these past few years. After discovering that this is &lt;em&gt;valid JS&lt;/em&gt; &lt;a href=&quot;https://caniuse.com/?search=dynamic%20import&quot;&gt;across all major browsers&lt;/a&gt;...&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; js&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; import&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;script.js&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...I had to make a post about it. So let&apos;s explore&lt;/p&gt;&lt;ol&gt;&lt;li&gt;🥞 My misconceptions about what bundlers do these days&lt;/li&gt;&lt;li&gt;🧩 What ES Modules + dynamic imports can do&lt;/li&gt;&lt;li&gt;🚀 How build tools are evolving for the post-IE era&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Onwards!&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;Foreward: Personal delusions in a webpack world&lt;/h2&gt;&lt;p&gt;What I&apos;m sharing here is probably common knowledge to some. Heck, import-able JavaScript modules have lurked in the &lt;a href=&quot;https://stackoverflow.com/a/33748435&quot;&gt;ECMAScript&lt;/a&gt; standard &lt;a href=&quot;https://medium.com/@giltayar/native-es-modules-ready-for-prime-time-87c64d294d3c&quot;&gt;since 2017&lt;/a&gt;! But if you&apos;ve been using &quot;traditional&quot; project configs like &lt;code&gt;create-react-app&lt;/code&gt; for a long time, you might think that old-school bundling is how the world works.&lt;/p&gt;&lt;p&gt;So let me &lt;em&gt;ahem&lt;/em&gt; unpack the traditional definition of &quot;bundling.&quot; In short, it&apos;s the concept of taking a chain of JS files like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// toppings.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  blueberries: async () =&amp;gt; await fetch(&apos;fresh-from-the-farm&apos;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  syrup = &quot;maple&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// ingredients.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; { flour: &apos;white&apos;, eggs: &apos;free-range&apos;, milk: &apos;2%&apos;, butter: &apos;dairy-free&apos; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// pancake.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { blueberries, syrup } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;./toppings&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { flour, eggs, milk, butter } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;./ingredients&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; pancake&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Pancake&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pancake.&lt;/span&gt;&lt;span&gt;mixItUp&lt;/span&gt;&lt;span&gt;([ flour, eggs, milk, butter ])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pancake.&lt;/span&gt;&lt;span&gt;cook&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pancake.&lt;/span&gt;&lt;span&gt;applyToppings&lt;/span&gt;&lt;span&gt;([ blueberries, syrup ])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And &quot;flattening&quot; the import / export chains into a big bundle pancake 🥞&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// bundler-output-alksdfjfsadlf.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; toppings__chunk&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  blueberries&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;fresh-from-the-farm&apos;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  syrup &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;maple&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; ingredients__chunk&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; { flour: &lt;/span&gt;&lt;span&gt;&apos;white&apos;&lt;/span&gt;&lt;span&gt;, eggs: &lt;/span&gt;&lt;span&gt;&apos;free-range&apos;&lt;/span&gt;&lt;span&gt;, milk: &lt;/span&gt;&lt;span&gt;&apos;2%&apos;&lt;/span&gt;&lt;span&gt;, butter: &lt;/span&gt;&lt;span&gt;&apos;dairy-free&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;blueberries&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;syrup&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; toppings__chunk&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;flour&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;eggs&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;milk&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;butter&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ingredients__chunk&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; pancake&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Pancake&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pancake.&lt;/span&gt;&lt;span&gt;mixItUp&lt;/span&gt;&lt;span&gt;([ flour, eggs, milk, butter ])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pancake.&lt;/span&gt;&lt;span&gt;cook&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pancake.&lt;/span&gt;&lt;span&gt;applyToppings&lt;/span&gt;&lt;span&gt;([ blueberries, syrup ])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So we&apos;re compressing all the JavaScript files we&apos;re developing into a &lt;em&gt;single&lt;/em&gt; file for the browser to consume. Back in 2015-era web development, this really was the only way to pull off &quot;importing&quot; one JS file into another. &lt;code&gt;import&lt;/code&gt; wasn&apos;t even valid JavaScript! It was just some neat trickery that build tools like webpack could pick up and understand.&lt;/p&gt;&lt;p&gt;But silently, in the depths of the ES spec, &lt;code&gt;import&lt;/code&gt; and &lt;code&gt;export&lt;/code&gt; syntax &lt;em&gt;did&lt;/em&gt; become valid JavaScript. Almost overnight, it became feasible to leave all your &lt;code&gt;import&lt;/code&gt; and &lt;code&gt;export&lt;/code&gt; statements in your code or even &lt;em&gt;gasp&lt;/em&gt; ditch your JS bundler entirely 😨&lt;/p&gt;&lt;p&gt;This innovation became what we call &lt;strong&gt;modules.&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;ES Modules&lt;/h2&gt;&lt;p&gt;There&apos;s an &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#other_differences_between_modules_and_standard_scripts&quot;&gt;in-depth article from MDN&lt;/a&gt; on this topic that&apos;s &lt;em&gt;well&lt;/em&gt; worth the read. But in short, &quot;ES Modules&quot; (sometimes denoted by &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#aside_%E2%80%94_.mjs_versus_.js&quot;&gt;&lt;code&gt;.mjs&lt;/code&gt; files&lt;/a&gt;) are JavaScript files with some exported values for others to import and use. As long as you load your &quot;entry&quot; files with the &lt;code&gt;type=&quot;module&quot;&lt;/code&gt; attribute:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;module&quot;&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;pancake.js&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That file is ready to &lt;code&gt;import&lt;/code&gt; all the other scripts it wants! Well, as long as those other scripts exist in your project&apos;s build of course (we&apos;ll ignore CORS issues for now 😁).&lt;/p&gt;&lt;p&gt;This concept of importing what&apos;s needed over &quot;flattening all the things&quot; has some nice benefits:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;You don&apos;t need to load and parse &lt;em&gt;everything&lt;/em&gt; up front.&lt;/strong&gt; By default, anything &lt;code&gt;import&lt;/code&gt;ed is &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#other_differences_between_modules_and_standard_scripts&quot;&gt;&quot;deferred&quot; for loading as needed&lt;/a&gt;. In other words, your computer won&apos;t turn into a fighter jet trying to load JS when you first visit your website.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The need for tooling like webpack can (one day) disappear ✨&lt;/strong&gt; Bringing browsers closer to how &lt;em&gt;humans&lt;/em&gt; write their code is a huge win for newbies and pros alike 🏆&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Dynamic imports take it a step further&lt;/h3&gt;&lt;p&gt;Dynamic imports are the spicier side of ES Modules that &lt;em&gt;really&lt;/em&gt; make things interesting. As &lt;a href=&quot;https://v8.dev/features/dynamic-import&quot;&gt;this article from the V8 team&lt;/a&gt; describes (creators of Google Chrome&apos;s rendering engine), a &lt;strong&gt;dynamic import&lt;/strong&gt; is an asynchronous fetch for some JavaScript whenever you need it.&lt;/p&gt;&lt;p&gt;It&apos;s very similar to the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;&lt;code&gt;fetch&lt;/code&gt; API&lt;/a&gt; in a way! But instread of grabbing some JSON or plain text, we&apos;re grabbing some real, &lt;em&gt;executable&lt;/em&gt; code that we want to run.&lt;/p&gt;&lt;p&gt;All you need is a humble one-liner:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; lookAtTheTime&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; import&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;./fashionably-late.js&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...and you just grabbed all the &lt;code&gt;export&lt;/code&gt;s from that file. Loading JS on-the-fly like this has a &lt;em&gt;ton&lt;/em&gt; of benefits if you&apos;re working with &lt;a href=&quot;https://www.bloomreach.com/en/blog/2018/07/what-is-a-single-page-application.html&quot;&gt;single page apps&lt;/a&gt; like NextJS or &lt;code&gt;create-react-app&lt;/code&gt;. The V8 team offered this elegantly simple take on client-side routing, only loading the JS you need when you click on a link:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; links&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelectorAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;nav &amp;gt; a&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; link&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; links) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  link.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;click&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // go grab whatever JS the route may need&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; module&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; import&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;href&lt;/span&gt;&lt;span&gt;}/script.mjs`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // The module exports a function named `loadPageInto`,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // Which might render some HTML into the body&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      module&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;loadPageInto&lt;/span&gt;&lt;span&gt;(document.body);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      document.body.innerHTML &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;p&amp;gt;404 page not found&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      `&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;I basically just implemented a router in 10 lines of code.&lt;/strong&gt; (yes, that&apos;s a &lt;em&gt;serious&lt;/em&gt; overstatement, but it&apos;s closer than you might think).&lt;/p&gt;&lt;p&gt;This falls into &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Code_splitting&quot;&gt;code splitting&lt;/a&gt;, aka loading &quot;components&quot; (or modules) of code whenever the user needs them. Back in the dark ages of bundle all-the-things, you&apos;d have to load all these components up front. This could mean &lt;em&gt;thousands&lt;/em&gt; of lines of dead code!&lt;/p&gt;&lt;h2&gt;So wait, it&apos;s 2021... why does all my tooling look the same?&lt;/h2&gt;&lt;p&gt;This was certainly my first question when I read up on this. I recently graduated from &lt;a href=&quot;https://create-react-app.dev/&quot;&gt;&lt;code&gt;create-react-app&lt;/code&gt;&lt;/a&gt; to &lt;a href=&quot;https://nextjs.org/&quot;&gt;NextJS&lt;/a&gt; as my React boilerplate go-to, but there&apos;s still that same webpack configuration + bundle process to think about 🤷‍♀️&lt;/p&gt;&lt;p&gt;A lot of this is just the curse of abstraction. Looking under the hood, these tools have made &lt;em&gt;great&lt;/em&gt; strides since ES modules hit the scene. Namely, tools like NextJS can magically &quot;split&quot; your React app into bite-sized chunks that get loaded as-needed. This means:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;only load the JS for a page &lt;strong&gt;when you actually visit that page&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;only load React components &lt;strong&gt;when they actually need to display&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;(bonus) pre-fetch JS &lt;strong&gt;when someone is &lt;em&gt;likely&lt;/em&gt; to need it.&lt;/strong&gt; This is a more advanced feature (&lt;a href=&quot;https://nextjs.org/docs/api-reference/next/link#if-the-route-has-dynamic-segments&quot;&gt;documented here&lt;/a&gt;), but it lets you do all sorts of craziness; say, grabbing resources for a page when you hover over  link&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;There&apos;s also the benefit of &lt;strong&gt;backwards compatibility&lt;/strong&gt; when using a bundler. For instance, Internet Explorer has no concept of &quot;modules&quot; or &quot;import&quot; statements, so any attempt to code split will blow up in your face 😬 But with a meta-framework like NextJS by your side, you can polyfill such use cases without having to think about it.&lt;/p&gt;&lt;h2&gt;Approaching the post-IE age&lt;/h2&gt;&lt;p&gt;If you haven&apos;t heard, a major announcement sent ripples through the web dev community recently: &lt;strong&gt;&lt;a href=&quot;https://techcommunity.microsoft.com/t5/microsoft-365-blog/microsoft-365-apps-say-farewell-to-internet-explorer-11-and/ba-p/1591666&quot;&gt;Microsoft will officially drop IE 11 support for its products in August 2021&lt;/a&gt;&lt;/strong&gt; 😱&lt;/p&gt;&lt;p&gt;Many are treating this as the ticking timebomb for legacy browser support. When it goes off... we might be safe to lose our polyfills for good. Yes, certain sites for governments and internal business operations will probably stick to their PHP-laced guns. But for us bleeding-edge developers, we may have a whole new frontier to explore 🚀&lt;/p&gt;&lt;h3&gt;A world of bundlers that... don&apos;t bundle&lt;/h3&gt;&lt;p&gt;The tides have certainly shifted in the JS bundler community in the past year. With the prospect of dropping polyfills and aggressive bundling for good, people started turning to the &lt;em&gt;real&lt;/em&gt; reasons you want a bundler:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;To process all your fanciness that &lt;em&gt;isn&apos;t&lt;/em&gt; valid JS.&lt;/strong&gt; Think JSX for React components, TypeScript for type checking, Styled Components and CSS modules for CSS-in-JS, etc etc.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;To spin up your app locally.&lt;/strong&gt; You could always open HTML files in your browser directly, but you&apos;ll loose all that immediate feedback! You should see all your new JS and CSS the millisecond you hit &quot;save.&quot;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;To optimize code for production.&lt;/strong&gt; You&apos;ll probably want some last-minute stripping for added speed, like removing &lt;code&gt;console.log&lt;/code&gt;s, minifying everything, linting, and so on.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Because of this refined feature set, the new wave of JS processors are just calling themselves &quot;build tools&quot; to stay more generalized.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.snowpack.dev/&quot;&gt;&lt;strong&gt;Snowpack&lt;/strong&gt;&lt;/a&gt; is really what got the ball rolling from my perspective. They promise all the selling points I listed above, plus the absolute fastest live-reloading in the biz. This is mainly because of that code splitting I mentioned earlier. Since they leave all those modules and dynamic imports in-tact, &lt;strong&gt;they avoid re-processing the JavaScript that didn&apos;t change.&lt;/strong&gt; So if you just updated a single React component, it&apos;ll reprocess those 10 lines of code and blast it onto the page in a flash ⚡️&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/vitejs/vite&quot;&gt;&lt;strong&gt;Vite&lt;/strong&gt;&lt;/a&gt; is a major contender to note as well. This one was spearheaded by &lt;a href=&quot;https://twitter.com/youyuxi?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor&quot;&gt;Evan You&lt;/a&gt; (the overlord of VueJS) to tackle a similar feature set to Snowpack. It&apos;s far too early to say whether I&apos;d &lt;em&gt;prefer&lt;/em&gt; this setup to Snowpack, but &lt;a href=&quot;https://blog.logrocket.com/vite-vs-snowpack-a-comparison-of-frontend-build-tools/&quot;&gt;here&apos;s a nice comparison piece&lt;/a&gt; if you&apos;re considering either for serious apps.&lt;/p&gt;&lt;p&gt;There&apos;s also the crazy world of using different programming languages to process your code. &lt;a href=&quot;https://github.com/evanw/esbuild&quot;&gt;ESBuild&lt;/a&gt; is a big contender right now, using GoLang to process JavaScript in no time flat.&lt;/p&gt;&lt;h3&gt;Call to action: explore these new build tools!&lt;/h3&gt;&lt;p&gt;It&apos;s definitely worth your time to whip up a sandbox and start compiling some code. &lt;a href=&quot;https://github.com/vitejs/vite/tree/main/packages/create-app&quot;&gt;Vite&apos;s &lt;code&gt;create-app&lt;/code&gt; tool&lt;/a&gt; is a great one for it&apos;s beginner friendliness, with options to use any major framework out-of-the-box (React, Vue, Svelte, and even &lt;a href=&quot;https://lit-element.polymer-project.org/&quot;&gt;Lit Element&lt;/a&gt;!).&lt;/p&gt;&lt;p&gt;I was caught off guard to find &lt;strong&gt;there&apos;s no build directory&lt;/strong&gt; when working in development. The code your write gets mapped to the browser directly, processed on-the-fly whenever you save ❤️&lt;/p&gt;&lt;p&gt;So go forth and see what the future is like! With any luck, we&apos;ll get to have our bundle pancake and eat it too 😁&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>4 Git shortcuts that define my workflow</title><link>https://bholmes.dev/blog/4-git-shortcuts-that-define-my-workflow/</link><guid isPermaLink="true">https://bholmes.dev/blog/4-git-shortcuts-that-define-my-workflow/</guid><description>Here&apos;s 4 bash snippets that let me push, pop, and pull my way to victory as a web dev!</description><pubDate>Thu, 18 Feb 2021 23:38:56 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;I started my first full-time job as a programmer this year, which means I&apos;ve been picking up &lt;em&gt;a lot&lt;/em&gt; of new tricks in a pretty quick timeframe. Naturally, I started automating the workflows I use day-in and day-out 😁&lt;/p&gt;&lt;p&gt;Stop me if you&apos;ve seen this workflow before:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;I picked up a ticket on JIRA&lt;/li&gt;&lt;li&gt;I need to pull the latest main branch&lt;/li&gt;&lt;li&gt;I need to checkout a new branch&lt;/li&gt;&lt;li&gt;I need to push that branch to origin before opening my PR&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I&apos;ll probably do this 5+ times in one day if we&apos;re in a bug-squashing flow. But when I&apos;m hurrying, it&apos;s so easy to either &lt;strong&gt;a)&lt;/strong&gt; work off an old &quot;main&quot; branch, or &lt;strong&gt;b)&lt;/strong&gt; do the &lt;strong&gt;copy-paste of shame&lt;/strong&gt; before your PR:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;To push the current branch and set the remote as upstream, use&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    git push --set-upstream origin crap-not-again&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;You know you cringe a little every time this pops up&lt;/em&gt; 😬&lt;/p&gt;&lt;p&gt;So let&apos;s discuss:&lt;/p&gt;&lt;p&gt;🍾 The importance of pushing and popping your stash&lt;/p&gt;&lt;p&gt;🙈 The joys of &lt;code&gt;upstream HEAD&lt;/code&gt;&lt;/p&gt;&lt;p&gt;🧶 Writing aliases for your terminal to tie it all together&lt;/p&gt;&lt;p&gt;&lt;em&gt;Onwards!&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;The fantastic 4 aliases&lt;/h2&gt;&lt;p&gt;Alright, let&apos;s do the big reveal. Here&apos;s my 4 shortcuts for slamming through daily tasks 💪&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Stash what I&apos;m working on and checkout the latest master&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;alias&lt;/span&gt;&lt;span&gt; gimme&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;git stash push -u &amp;amp;&amp;amp; git checkout main &amp;amp;&amp;amp; git pull -r&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Grab the latest master and come back to the branch I was on (with stashing!)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;alias&lt;/span&gt;&lt;span&gt; yoink&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;gimme &amp;amp;&amp;amp; git checkout - &amp;amp;&amp;amp; git stash pop&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Checkout a new branch and push it to origin (so I don&apos;t forget that set-upstream)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;woosh&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  git&lt;/span&gt;&lt;span&gt; checkout&lt;/span&gt;&lt;span&gt; -b&lt;/span&gt;&lt;span&gt; $1&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; push&lt;/span&gt;&lt;span&gt; -u&lt;/span&gt;&lt;span&gt; origin&lt;/span&gt;&lt;span&gt; HEAD&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# ALL TOGETHER NOW&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Stash my current WIP, checkout a new branch off the latest master, and push it to origin&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;alias&lt;/span&gt;&lt;span&gt; boop&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;gimme &amp;amp;&amp;amp; woosh&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These commands are written in &lt;code&gt;bash&lt;/code&gt;. So if you want to use these in your own terminal, go find your &lt;code&gt;bashrc&lt;/code&gt; and paste away! This is usually found in your user directory: &lt;code&gt;~/.bashrc&lt;/code&gt;. And for all you windows users out there: you&apos;ll probably need to &lt;a href=&quot;https://git-scm.com/downloads&quot;&gt;install the Git Bash terminal&lt;/a&gt; (included with Git) and create the file &lt;a href=&quot;https://stackoverflow.com/questions/6883760/git-for-windows-bashrc-or-equivalent-configuration-files-for-git-bash-shell&quot;&gt;like so&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;✋ But wait!&lt;/strong&gt; Before you blindly copy these, let&apos;s unpack each of them a little.&lt;/p&gt;&lt;h2&gt;&lt;code&gt;gimme&lt;/code&gt; ✊&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;alias&lt;/span&gt;&lt;span&gt; gimme&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;git stash push -u &amp;amp;&amp;amp; git checkout main &amp;amp;&amp;amp; git pull -r&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is my &quot;return to home base&quot; command. It assumes you might &lt;em&gt;not&lt;/em&gt; be on the main branch just yet, so it&apos;ll stash everything you&apos;re working on using the &lt;code&gt;stash&lt;/code&gt; command. It also saves &quot;untracked&quot; / new files with the &lt;code&gt;-u&lt;/code&gt; flag.&lt;/p&gt;&lt;p&gt;Next, it checks out your &lt;code&gt;main&lt;/code&gt; branch and pulls the latest contents. That &lt;code&gt;-r&lt;/code&gt; flag will be sure to &quot;rebase&quot; onto the latest, preventing unecessary merge conflicts.&lt;/p&gt;&lt;p&gt;This is the simplest command of the bunch, but it&apos;s also my most-used! On my team, we run a lot of automated scripts on &lt;code&gt;pull&lt;/code&gt; for installing dependencies and pulling content from our &lt;a href=&quot;https://www.optimizely.com/optimization-glossary/content-management-system/&quot;&gt;CMS&lt;/a&gt;. So, it&apos;s super useful to return to &lt;code&gt;main&lt;/code&gt; and grab all this new content in one go.&lt;/p&gt;&lt;h2&gt;&lt;code&gt;yoink&lt;/code&gt; 🎣&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;alias&lt;/span&gt;&lt;span&gt; yoink&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;gimme &amp;amp;&amp;amp; git checkout - &amp;amp;&amp;amp; git stash pop&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This phrase is certainly a close relative to &lt;code&gt;gimme&lt;/code&gt;. For some etymology, I&apos;ll include the &lt;a href=&quot;https://www.urbandictionary.com/define.php?term=Yoink&quot;&gt;urban dictionary entry&lt;/a&gt; on what it means to &lt;em&gt;yoink:&lt;/em&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;An exclamation that, when uttered in conjunction with taking an object, immediately transfers ownership from the original owner to the person using the word regardless of previous property rights.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;...In other words, we are grabbing the main branch and bringing it back to the branch we&apos;re on 😁 We pull this off using the &lt;code&gt;git checkout -&lt;/code&gt; command after running &lt;code&gt;gimme&lt;/code&gt;. This is super convenient for grabbing the latest changes to &lt;a href=&quot;https://egghead.io/lessons/git-push-a-rebased-local-branch-by-using-force-with-lease&quot;&gt;force rebase the branch&lt;/a&gt; we&apos;re working on.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;💡 Note:&lt;/strong&gt; is easy to achieve with a &lt;code&gt;fetch&lt;/code&gt; command as well:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt; origin&lt;/span&gt;&lt;span&gt; master:master&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...but if your team runs automated scripts whenever you &lt;code&gt;pull&lt;/code&gt;, you&apos;ll probably want the original command I described.&lt;/p&gt;&lt;h3&gt;Clarification on using &lt;code&gt;stash&lt;/code&gt; vs &lt;code&gt;stash push&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;You also may be new to using &lt;code&gt;push&lt;/code&gt; and &lt;code&gt;pop&lt;/code&gt; on your stash, instead of &lt;code&gt;apply&lt;/code&gt; and &lt;code&gt;clear&lt;/code&gt; (as I often used to do). Here&apos;s the distinction:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Whenever you run &quot;vanilla&quot; &lt;code&gt;git stash&lt;/code&gt;, &lt;strong&gt;you&apos;re &lt;em&gt;merging&lt;/em&gt; your current changes&lt;/strong&gt; with all the other changes in the stash. Think of this like throwing all your belongings in a bag, and &lt;code&gt;git stash apply&lt;/code&gt; is like dumping everything out onto the floor.&lt;/li&gt;&lt;li&gt;Whenever you run &lt;code&gt;git stash push&lt;/code&gt;, you&apos;re &lt;strong&gt;putting your current changes in a &lt;em&gt;separate compartment&lt;/em&gt;&lt;/strong&gt; from all the other changes in the stash. Think of this like choosing a different bag pocket every time you go to &quot;stash&quot; something. Then, when you &lt;code&gt;pop&lt;/code&gt;, you pull from the last pocket you just used.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This is the reason we use &lt;code&gt;push&lt;/code&gt; inside the &lt;strong&gt;&lt;code&gt;gimme&lt;/code&gt; command,&lt;/strong&gt; and &lt;code&gt;pop&lt;/code&gt; inside the &lt;strong&gt;&lt;code&gt;yoink&lt;/code&gt; command.&lt;/strong&gt; It guarantees that we grab the same thing we just stashed, without getting mixed up with anything &lt;em&gt;already in&lt;/em&gt; your stash. If you&apos;ve ever dug around your bag trying to find one tiny thing stuck at the bottom, you know what I&apos;m talking about here 😁&lt;/p&gt;&lt;h2&gt;&lt;code&gt;woosh&lt;/code&gt; 💨&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;woosh&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  git&lt;/span&gt;&lt;span&gt; checkout&lt;/span&gt;&lt;span&gt; -b&lt;/span&gt;&lt;span&gt; $1&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; push&lt;/span&gt;&lt;span&gt; -u&lt;/span&gt;&lt;span&gt; origin&lt;/span&gt;&lt;span&gt; HEAD&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is the solution to your &lt;code&gt;--set-upstream&lt;/code&gt; woes. Instead of pushing to origin later, this lets you check out a new branch and push &lt;em&gt;right away&lt;/em&gt; so you don&apos;t forget.&lt;/p&gt;&lt;p&gt;Yes, there&apos;s some cases when you don&apos;t want your local branch on the remote just yet, but this is pretty rare in my experience. I often create a branch corresponding to a given task on our Kanban board that I&apos;m ready to push up right away.&lt;/p&gt;&lt;p&gt;And if you&apos;ve never seen the &lt;code&gt;HEAD&lt;/code&gt; parameter before... &lt;strong&gt;remember that one!&lt;/strong&gt; It&apos;s a super slick way to auto-fill the name of your current branch instead of typing it out by hand 🔥&lt;/p&gt;&lt;h2&gt;&lt;code&gt;boop&lt;/code&gt; 👇&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;alias&lt;/span&gt;&lt;span&gt; boop&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;gimme &amp;amp;&amp;amp; woosh&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Shout out to &quot;lord of boops&quot; &lt;a href=&quot;https://www.jason.af/&quot;&gt;Jason Lengstorf&lt;/a&gt; for the inspiration&lt;/em&gt;&lt;/p&gt;&lt;p&gt;This command goes full-circle. If you&apos;re somehow too lazy to check out the latest &lt;code&gt;main&lt;/code&gt; branch before starting anew, this will let you &lt;code&gt;gimme&lt;/code&gt; and &lt;code&gt;woosh&lt;/code&gt; all in one go!&lt;/p&gt;&lt;p&gt;Not much to explain with this one. Just go forth into perfect boop-dom.&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>4 Conference talks that changed my perspective as a web dev</title><link>https://bholmes.dev/blog/conference-talks-that-changed-my-perspective-as-a-web-dev/</link><guid isPermaLink="true">https://bholmes.dev/blog/conference-talks-that-changed-my-perspective-as-a-web-dev/</guid><description>Let&apos;s explore architecture complexity, when abstractions are bad, how React Hooks work, and the importance of SvelteJS</description><pubDate>Wed, 10 Feb 2021 21:44:22 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;I&apos;ve resolved to make 2021 my biggest year of learning yet. My blogging game has been pretty sporadic up until now... but I&apos;ve finally decided to go all-in on the &lt;a href=&quot;https://www.swyx.io/learn-in-public/&quot;&gt;learning in public mantra&lt;/a&gt; with 1 post a week!&lt;/p&gt;&lt;p&gt;I used to believe blog posts had to be shining nuggets of wisdom only I could find. But with all the notes I take on a weekly basis, why not share my journal with the world too? 😁&lt;/p&gt;&lt;p&gt;So, here&apos;s some of the most impactful conference talks I&apos;ve found in the last year or so. I hope to check-in with more entries in the future, and I&apos;d love to &lt;strong&gt;hear your own favorite talks&lt;/strong&gt; in the comments!&lt;/p&gt;&lt;h2&gt;Rich Hickey - Simple Made Easy 🧶&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://www.infoq.com/presentations/Simple-Made-Easy/&quot;&gt;&lt;strong&gt;🎥 View the talk + transcript here&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;If you&apos;ve ever heard the someone say that something is &quot;easy, but not simple,&quot; this is probably the talk they&apos;re referencing. This is an easy recommend to programmers in general (not just web devs). That said, I think this talk is &lt;em&gt;especially&lt;/em&gt; relevant to frontend-ers nowadays with all the tools at our disposal.&lt;/p&gt;&lt;p&gt;It feels like web frameworks and &quot;best practices&quot; are moving towards some powerful new opinions:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Everything is a component&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Functional programming is king&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;State management is complex&lt;/strong&gt; and deserves a rethink (&lt;a href=&quot;https://www.youtube.com/watch?v=hiT4Q1ntvzg&quot;&gt;hello state machines&lt;/a&gt; 👋)&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;☝️ These are the points Rich was getting at with this talk &lt;em&gt;a decade ago!&lt;/em&gt; This is why I&apos;ve resisted this talk multiple times throughout my web dev journey. As a junior getting comfortable with enterprise-grade React apps, it&apos;s helped me understand the &lt;em&gt;why&lt;/em&gt; behind my team&apos;s architectural decisions.&lt;/p&gt;&lt;h3&gt;Personal notes&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simple&lt;/strong&gt; is an objective measure, no matter the person, reflecting how many &lt;strong&gt;interwoven pieces&lt;/strong&gt; (complexity) there are in a given system&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Easy&lt;/strong&gt; is relative to every individual, reflecting how &quot;familiar&quot; or &quot;near at hand&quot; something feels&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Agile programming encourages us to move fast without taking a step back&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Complexity and tech debt pile up when we ignore signs of bad abstraction&lt;/li&gt;&lt;li&gt;Favorite quote of the talk: &lt;em&gt;&quot;But programmers fire the starting pistol every 100 yards and call it a (new) sprint!&quot;&lt;/em&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Replace &lt;strong&gt;complecting&lt;/strong&gt; (knotted-up code with lots of interdependent pieces) with &lt;strong&gt;composing&lt;/strong&gt; (modularity a la modern frontend frameworks)&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Think building a castle from Legos instead of a &quot;knitted castle&quot; from interweaving&lt;/li&gt;&lt;li&gt;Separate horizontally, stratify vertically&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Concrete improvements to make&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&quot;Stateful&quot; variables complect &lt;em&gt;values&lt;/em&gt; with &lt;em&gt;change overtime&lt;/em&gt;&lt;/strong&gt;; make transition from one state to another predictable (see &lt;a href=&quot;https://www.youtube.com/watch?v=hiT4Q1ntvzg&quot;&gt;state machines&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Stay close to what the system does;&lt;/strong&gt; consider the &lt;em&gt;behavior&lt;/em&gt; over &lt;em&gt;implementation details&lt;/em&gt;&lt;ul&gt;&lt;li&gt;Example: &lt;a href=&quot;https://www.freecodecamp.org/news/imperative-vs-declarative-programming-difference/&quot;&gt;Declarative over imperative programming&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Example: test-driven-development done right (&lt;a href=&quot;https://www.youtube.com/watch?v=EZ05e7EMOLM&quot;&gt;incredible talk by Ian Cooper here&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Work with &lt;em&gt;rules&lt;/em&gt; over conditional / switch case spaghetti&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;Given some data X, here&apos;s some rules to make it become Y&lt;/li&gt;&lt;li&gt;Lean on &lt;a href=&quot;https://www.freecodecamp.org/news/what-is-a-pure-function-in-javascript-acb887375dfe/&quot;&gt;pure functions&lt;/a&gt;, which give you a consistent output for a given input&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Dan Abramov - The Wet Codebase 🌊&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://www.deconstructconf.com/2019/dan-abramov-the-wet-codebase&quot;&gt;&lt;strong&gt;🎥 View the talk + transcript here&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Here&apos;s another architecture-y talk that also reaches far beyond web development. If you&apos;re unfamiliar with &lt;a href=&quot;https://overreacted.io/&quot;&gt;Dan Abramov&lt;/a&gt;, he&apos;s one of the most prolific members of the React core team from a teaching standpoint alone. So if you want advice on architecting your web apps, this is your guy 😁&lt;/p&gt;&lt;p&gt;This talk goes hand-in-hand with &lt;a href=&quot;https://kentcdodds.com/blog/aha-programming&quot;&gt;Kent C Dodd&apos;s post on &quot;AHA programming&quot;&lt;/a&gt;. Generally, they&apos;re both addressing the biggest pitfall of component-based thinking: copy / paste feels like bad practice, so you abstract &lt;em&gt;every&lt;/em&gt; piece of logic to its own little file.&lt;/p&gt;&lt;p&gt;Sure there&apos;s a place for abstraction, but there&apos;s &lt;em&gt;also&lt;/em&gt; a place for duplication! This talk has a lot of examples and funny quotables to keep things light; definitely worth the watch.&lt;/p&gt;&lt;h3&gt;Personal notes&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;If left unchecked, abstractions can become &lt;em&gt;Frankenstein&lt;/em&gt; code overtime&lt;ul&gt;&lt;li&gt;An abstraction &lt;em&gt;almost&lt;/em&gt; fits are use case, but not quite 👉 we wittle away that round hole to fit our square peg&lt;/li&gt;&lt;li&gt;When bugs arise for &lt;em&gt;one&lt;/em&gt; use case, we introduce fixes affecting &lt;em&gt;tons&lt;/em&gt; of other use cases&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;👍 When abstraction is good&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Makes your code more declarative&lt;/strong&gt; / focus on a specific intent (see that Rich Hickey talk above 😉)&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Avoids missed bug fixes&lt;/strong&gt; 👉 fix it once, fix everywhere&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;👎 When abstraction is bad&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Creates coupling&lt;/strong&gt; - when it doesn&apos;t &lt;em&gt;quite&lt;/em&gt; fit, you can create a monster of refactors&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Adds indirection&lt;/strong&gt; - creates layers and layers overtime; &lt;em&gt;&quot;We avoid spaghetti code, but we create lasagna code&quot;&lt;/em&gt; 🇮🇹&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;Ways to improve going forward&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Test code that &lt;em&gt;uses&lt;/em&gt; an abstraction&lt;/strong&gt;, not the abstraction itself&lt;ul&gt;&lt;li&gt;If you remove that abstraction later, your tests explode!&lt;/li&gt;&lt;li&gt;Abstractions are just another implementation detail (again, &lt;a href=&quot;https://www.youtube.com/watch?v=EZ05e7EMOLM&quot;&gt;TDD is king&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Don&apos;t commit to abstraction layers until you need them;&lt;/strong&gt; &lt;em&gt;&quot;If a girl is into the same obscure bands as you are... that doesn&apos;t mean you&apos;re meant to be together&quot;&lt;/em&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Be ready to remove abstractions later;&lt;/strong&gt; Be the one asking &lt;em&gt;&quot;Please inline this abstraction!&quot;&lt;/em&gt; in a PR review!&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Rich Harris - Rethinking Reactivity ⚛️&lt;/h2&gt;&lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/AdNJ3fydeao&quot; width=&quot;560&quot; height=&quot;315&quot; allowfullscreen&gt;  &lt;/iframe&gt;  &lt;p&gt;In my opinion, this is &lt;strong&gt;the biggest bombshell to drop&lt;/strong&gt; since React was first revealed 💣&lt;/p&gt;&lt;p&gt;A trigger warning is probably in order here: if you&apos;re a diehard React follower, this talk questions many practice React holds dear (including the virtual DOM itself!).&lt;/p&gt;&lt;p&gt;But even if you disagree with Rich&apos;s points, this talk is a &lt;em&gt;serious&lt;/em&gt; landmark in the web framework canon. It also exposes what &quot;bundlers,&quot; &quot;compilers,&quot; and &quot;reacting to change&quot; all &lt;em&gt;really&lt;/em&gt; mean under the hood. If you aren&apos;t convert to a Svelte fan after this, you&apos;ll at least understand where the web has been, and where it might be heading!&lt;/p&gt;&lt;h3&gt;Personal notes&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;What is reactive programming?&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;It all started with spreadsheets&lt;ul&gt;&lt;li&gt;I change a value in one cell, and other cells &quot;react&quot; to those changes with formulas&lt;/li&gt;&lt;li&gt;Earliest example of &lt;strong&gt;only re-rendering values that change&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;It&apos;s 1) about tracking values and 2) updating &lt;em&gt;dependents&lt;/em&gt; on that value&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Problem with React&apos;s model&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;When state changes in a component, that &lt;em&gt;whole component&lt;/em&gt; re-evaluates itself from the top&lt;/li&gt;&lt;li&gt;Treats your HTML like a black box; apply the change, then diff against the previous chunk&lt;/li&gt;&lt;li&gt;Really, React doesn&apos;t know about your &quot;state values&quot; or how they affect the DOM!&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Bad signs for efficiency:&lt;/strong&gt; I shouldn&apos;t need &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt;, &lt;code&gt;shouldComponentUpdate&lt;/code&gt;, etc&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;Instead of opting &lt;em&gt;out&lt;/em&gt; of reevaluating state (a la &lt;code&gt;useMemo&lt;/code&gt;), we could opt &lt;em&gt;in&lt;/em&gt; by flagging state variables that depend on other state variables&lt;ul&gt;&lt;li&gt;Much like a spreadsheet; write formulas that flag which variables affect a given value&lt;/li&gt;&lt;li&gt;Svelte uses a custom &lt;code&gt;$:&lt;/code&gt; operator to &quot;flag&quot; state that is computed from &lt;em&gt;other&lt;/em&gt; state &lt;a href=&quot;https://svelte.dev/tutorial/reactive-declarations&quot;&gt;(example here)&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Svelte is a compiler, not a runtime&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;In other words, a &quot;Svelte&quot; component compiles to JS your browser understand&lt;/li&gt;&lt;li&gt;No &quot;runtime&quot; (like React&apos;s virtual DOM) needs to get imported&lt;/li&gt;&lt;li&gt;Also means Svelte can &lt;strong&gt;bend the JS language to its will&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;Evan You, creator of VueJS - Svelte is too magical, since it lets you write JavaScript that isn&apos;t totally valid&lt;/li&gt;&lt;li&gt;Rich Harris&apos; response - this opinion is like believing HTML, CSS and JS should live in separate files. CSS-in-JS is weird too, so what&apos;s wrong with this?&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;Some other cool demos of Svelte&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://css-tricks.com/what-i-like-about-writing-styles-with-svelte/&quot;&gt;&lt;strong&gt;CSS scoping by component&lt;/strong&gt;&lt;/a&gt; just by using a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://svelte.dev/tutorial/transition&quot;&gt;&lt;strong&gt;Transition directives&lt;/strong&gt;&lt;/a&gt; with sensible out-of-the-box options&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Shawn &quot;swyx&quot; Wang - Getting Closure on React Hooks 🎣&lt;/h2&gt;&lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/KJP1E-Y-xyo&quot; width=&quot;560&quot; height=&quot;315&quot; allowfullscreen&gt;  &lt;/iframe&gt;  &lt;p&gt;This is a fast-paced and code-heavy talk, so you&apos;ll probably want 1x speed on this one.&lt;/p&gt;&lt;p&gt;That said... this is &lt;em&gt;the most&lt;/em&gt; enlightening talk I&apos;ve seen on React. Period. It&apos;s only 30 minutes long, but it gave me holistic understanding on how React hooks, state management, and re-rendering all work together. It also shows some huge use cases for &quot;closure&quot; in JS. If you have a web dev interview coming up, point to this! 😉&lt;/p&gt;&lt;h3&gt;Personal notes&lt;/h3&gt;&lt;p&gt;Hard to write a succinct, bulleted list for this one. So, I just annotated the finished product to explain how everything works. Fair warning: it&apos;s a &lt;em&gt;lot&lt;/em&gt; to take in!&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/YzpWGaz?editors=0011&quot;&gt;🚀 Functioning codepen to see it in action&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; React&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // create an array for all the state variables in our &quot;React app&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; stateValues &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // start our state index at 0. We&apos;ll use this&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // to throw state into that array ☝️ everytime someone calls &quot;useState&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; index &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;initValue&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // state should be set to either:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 1. the initial value (aka React.useState(initValue))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // if this is the first time our component rendered&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // 2. the value from the *last* render&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // if we&apos;re re-rendering our component (aka stateValues[index])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; stateValues[index] &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; initValue;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // &quot;freeze&quot; our index to this particular useState call with _index.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // prevents the index from changing before we try to call setState later!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; _index&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; index;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; setState&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;newValue&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      stateValues[_index] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; newValue;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // increment index so our next useState call doesn&apos;t override the state&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // we just stored above&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    index&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; [state, setState];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; render&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Component&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // every time we re-render our app,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // update all our state variables starting from the top&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    index &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; C&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Component&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // &quot;render&quot; the component (which calls the useState function)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    C&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; C&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // return all our functions from this foe React &quot;module&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; { useState, render };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Component&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setCount&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // whenever we &quot;render&quot; this component with React.render, &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // just log the value of our state variable&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    render&lt;/span&gt;&lt;span&gt;: () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;({ count }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    click&lt;/span&gt;&lt;span&gt;: () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setCount&lt;/span&gt;&lt;span&gt;(count &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; App &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(Component) &lt;/span&gt;&lt;span&gt;// logs { count: 2 }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;App.&lt;/span&gt;&lt;span&gt;click&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;// sets the state at stateValues[0] to 3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;App.&lt;/span&gt;&lt;span&gt;click&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;// sets the state at stateValues[0] to 4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;React.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(Component) &lt;/span&gt;&lt;span&gt;// logs { count: 4 }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/article&gt;</content:encoded></item><item><title>Another way to understand JavaScript&apos;s array.reduce</title><link>https://bholmes.dev/blog/another-way-to-understand-array-reduce/</link><guid isPermaLink="true">https://bholmes.dev/blog/another-way-to-understand-array-reduce/</guid><description>I struggled with understanding array.reduce for a while. So I found my own way of explaining it that clicks 💡</description><pubDate>Fri, 05 Feb 2021 16:26:05 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;If you&apos;ve run the guantlet of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#instance_methods&quot;&gt;array methods&lt;/a&gt; in JavaScript, you&apos;ve probably hit this roadblock a few times:&lt;/p&gt;&lt;p&gt;&lt;em&gt;Wait, how do I use the &lt;code&gt;reduce&lt;/code&gt; function again?&lt;/em&gt;&lt;/p&gt;&lt;p&gt;I actually led a JS bootcamp with this topic for my college&apos;s Hack4Impact chapter (&lt;a href=&quot;http://hack4impact.org/dev-bootcamp&quot;&gt;material 100% free-to-use here!&lt;/a&gt;). Questions on &lt;code&gt;reduce&lt;/code&gt; have come up so many times, and I think I&apos;ve finally found an explanation that clicks 😁 Hope it works for you too!&lt;/p&gt;&lt;h2&gt;🎥 Video walkthrough&lt;/h2&gt;&lt;p&gt;If you prefer to learn by video tutorial, this one&apos;s for you. You can &lt;a href=&quot;https://codepen.io/bholmesdev/pen/rXJpmr?editors=0010&quot;&gt;fork this CodePen&lt;/a&gt; for the source material to follow along 🏃‍♂️&lt;/p&gt;&lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/P9qlPEg_MKc&quot; width=&quot;560&quot; height=&quot;315&quot; allowfullscreen&gt;  &lt;/iframe&gt;  &lt;h2&gt;📝 Step-by-step cheatsheet&lt;/h2&gt;&lt;p&gt;Let&apos;s walk our way to &lt;code&gt;reduce&lt;/code&gt; by using what we know: good ole&apos; for loops.&lt;/p&gt;&lt;p&gt;Here&apos;s an example. Say we have our favorite album on a CD (remember those? 💿), and our stereo tells us the length of each track in minutes. Now, we want to figure out how long the &lt;em&gt;entire album&lt;/em&gt; is.&lt;/p&gt;&lt;p&gt;Here&apos;s a simplified approach for what we want to do:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// make a variable to keep track of the length, starting at 0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; albumLength &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// walk through the songs on the album...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;album.songs.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;song&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // and add the length of each song to our running total&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  albumLength &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; song.minutesLong&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;No too bad! Just loop over the songs, and &lt;strong&gt;accumulate&lt;/strong&gt; the album runtime while we walk through the songs. This is basically the process you&apos;d use in real life, tallying up the album length as you skip through the tracks on your stereo.&lt;/p&gt;&lt;p&gt;That word &quot;accumulate&quot; is pretty significant here though. In essence, we&apos;re taking this list of track lengths, and &lt;strong&gt;reducing&lt;/strong&gt; them to a single &lt;strong&gt;accumulated&lt;/strong&gt; number: the &lt;code&gt;albumLength&lt;/code&gt;. This process of &lt;strong&gt;reducing&lt;/strong&gt; to an &lt;strong&gt;accumulator&lt;/strong&gt; should set off a light bulb in your head: 💡 &lt;em&gt;we can use &lt;code&gt;array.reduce&lt;/code&gt;!&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;Going from &lt;code&gt;forEach&lt;/code&gt; to &lt;code&gt;reduce&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Let&apos;s try reduce-ifying our function from earlier. This is a simple, 4 step process:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Change &lt;code&gt;forEach&lt;/code&gt; to &lt;code&gt;reduce&lt;/code&gt;:&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; albumLength &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;album.songs.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;song&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  albumLength &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; albumLength &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; song.minutesLong&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;&lt;li&gt;Move &lt;code&gt;albumLength&lt;/code&gt; to the &lt;strong&gt;first parameter of the loop function&lt;/strong&gt;, and the initial value (0) to the &lt;strong&gt;second parameter of &lt;code&gt;reduce&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// accumulator up here 👇&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;album.songs.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;albumLength&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;song&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  albumLength &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; albumLength &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; song.minutesLong&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;// 👈 initial value here&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;&lt;li&gt;Change &lt;code&gt;albumLength = &lt;/code&gt; to a &lt;strong&gt;return statement.&lt;/strong&gt; This isn&apos;t too different conceptually, since we&apos;re still adding our song length onto our &quot;accumulated&quot; album length:&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;album.songs.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;albumLength&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;song&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 👇 Use &quot;return&quot; instead of our = assignment&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; albumLength &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; song.minutesLong&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;&lt;li&gt;Retrieve the result of our &lt;code&gt;reduce&lt;/code&gt; loop (aka our total album length). This is just the value returned:&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; totalAlbumLength&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; album.songs.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;albumLength&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;song&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; albumLength &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; song.minutesLong&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;And that&apos;s it!&lt;/strong&gt; 🎉&lt;/p&gt;&lt;h3&gt;So wait, why do I even need &lt;code&gt;reduce&lt;/code&gt;?&lt;/h3&gt;&lt;p&gt;After all that work, &lt;code&gt;reduce&lt;/code&gt; might feel like a slightly harder way of writing a &lt;code&gt;for&lt;/code&gt; loop. In a way... it kind of is 😆&lt;/p&gt;&lt;p&gt;It offers one key benefit though: &lt;strong&gt;since &lt;code&gt;reduce&lt;/code&gt; returns our total, function chaining is a lot easier.&lt;/strong&gt; This may not be a benefit you appreciate right away, but consider this more complex scenario:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Say we have this array of arrays,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// and we want to &quot;flatten&quot; everything to one big array of songs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; songsByAlbum&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt;&apos;Rap Snitches Knishes&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;Beef Rap&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;Gumbo&apos;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt;&apos;Accordion&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;Meat Grinder&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;Figaro&apos;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt;&apos;Fazers&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;Anti-Matter&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;Krazy World&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; songs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;songsByAlbum.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;albumSongs&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // &quot;spread&quot; the contents of each array into our big array using &quot;...&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  songs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;songs, &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;albumSongs]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This isn&apos;t too hard to understand. But what if we want to do some &lt;em&gt;more&lt;/em&gt; fancy array functions on that list of &lt;code&gt;songs&lt;/code&gt;?&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Ex. Make these MF DOOM songs titles all caps&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; songs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;songsByAlbum.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;albumSongs&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  songs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;songs, &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;albumSongs]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; uppercaseSongs&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; songs.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;song&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; song.&lt;/span&gt;&lt;span&gt;toUppercase&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is fine, but what if we could &quot;chain&quot; these 2 modifications &lt;em&gt;together&lt;/em&gt;?&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// grab our *final* result all the way at the start&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; uppercaseSongs&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt;&apos;Rap Snitches Knishes&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;Beef Rap&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;Gumbo&apos;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt;&apos;Accordion&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;Meat Grinder&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;Figaro&apos;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt;&apos;Fazers&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;Anti-Matter&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;Krazy World&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// rewriting our loop to a &quot;reduce,&quot; same way as before&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reduce&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;songs&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;albumSongs&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;songs, &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;albumSongs]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, [])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// then, map our songs right away!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;song&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; song.&lt;/span&gt;&lt;span&gt;toUppercase&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Woah!&lt;/em&gt; Throwing in a &lt;code&gt;reduce&lt;/code&gt;, we just removed our standalone variables for &lt;code&gt;songsByAlbum&lt;/code&gt; and &lt;code&gt;songs&lt;/code&gt; entirely 🤯&lt;/p&gt;&lt;p&gt;Take this example with a grain of salt though. &lt;strong&gt;This approach &lt;em&gt;can&lt;/em&gt; hurt the readability of your code&lt;/strong&gt; when you&apos;re still new to these array functions. So, just keep this &lt;code&gt;reduce&lt;/code&gt; function in your back pocket, and pull it out when you could really see it improving the quality of your code.&lt;/p&gt;&lt;p&gt;Also, if this helped you out, you can learn more about ❤️ &lt;a href=&quot;https://hack4impact.org/&quot;&gt;Hack4Impact here&lt;/a&gt;.&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>A shiny-on-hover effect that follows your mouse (CSS) ✨</title><link>https://bholmes.dev/blog/a-shiny-on-hover-effect-that-follows-your-mouse-css/</link><guid isPermaLink="true">https://bholmes.dev/blog/a-shiny-on-hover-effect-that-follows-your-mouse-css/</guid><description>Using CSS variables + any frontend framework to add a metallic shine to your UI</description><pubDate>Thu, 28 Jan 2021 17:40:13 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;Hover states are probably the most fun a developer can have when a designer isn&apos;t looking. You&apos;ve seen the basics at this point; fade-ins, growing and shrinking, color shifts, &lt;a href=&quot;https://www.joshwcomeau.com/react/rainbow-button/&quot;&gt;animated rainbow gradients&lt;/a&gt;, etc etc etc.&lt;/p&gt;&lt;p&gt;But there was one animation that inspired me recently (props &lt;a href=&quot;https://www.youtube.com/watch?v=VBkGe1TxEuI&amp;amp;t=225s&quot;&gt;to Keyframers&lt;/a&gt; for shouting it out!)&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;It&apos;s Astro Community Day! 🥳&lt;br /&gt;You might be wondering...&lt;br /&gt;&lt;br /&gt;Who are some 💯 Astro contributors?&lt;br /&gt;Where does Astro&apos;s sponsorship 💰 go?&lt;br /&gt;How does Astro give back to OSS?&lt;br /&gt;&lt;br /&gt;Let&apos;s get into it! A thread 🧵 &lt;a href=&quot;https://t.co/PrCThabHYM&quot;&gt;pic.twitter.com/PrCThabHYM&lt;/a&gt;&lt;/p&gt;— Astro (@astrodotbuild) &lt;a href=&quot;https://x.com/astrodotbuild/status/1512144306898976768?ref_src=twsrc%5Etfw&quot;&gt;April 7, 2022&lt;/a&gt;&lt;/blockquote&gt;

 &lt;p&gt;This isn&apos;t some &quot;static&quot; hover state that always looks the same. It actually &lt;em&gt;tracks your mouse moment&lt;/em&gt; to make the page even more interactive. This seemed like such a cool idea... that we threw it all over our &lt;a href=&quot;https://hack4impact.org/&quot;&gt;Hack4Impact&lt;/a&gt; site 😁&lt;/p&gt;&lt;p&gt;So let&apos;s explore&lt;/p&gt;&lt;ul&gt;&lt;li&gt;🎈 Why CSS variables can help us&lt;/li&gt;&lt;li&gt;✨ How we style our button&lt;/li&gt;&lt;li&gt;🪤 How we map mouse movements to a metallic shine&lt;/li&gt;&lt;li&gt;🔨 How to adapt this animation to any UI framework&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Onwards!&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;🎈 Brief primer on CSS variables&lt;/h2&gt;&lt;p&gt;In case you haven&apos;t heard, CSS variables are kind of taking web development by storm right now. They&apos;re a bit like those &lt;code&gt;$&lt;/code&gt; variables preprocessors like &lt;a href=&quot;https://sass-lang.com/&quot;&gt;SASS&lt;/a&gt; and &lt;a href=&quot;http://lesscss.org/&quot;&gt;LESS&lt;/a&gt; let you pull off, but with one huge benefit: &lt;strong&gt;you can change the value of these variables at runtime&lt;/strong&gt; using JavaScript 😱&lt;/p&gt;&lt;p&gt;Let&apos;s see a simple example. Say we want to make a balloon pump, where you hit a button as fast as you can to &quot;inflate&quot; an HTML-style balloon.&lt;/p&gt;&lt;p&gt;If we didn&apos;t know anything about CSS variables, we&apos;d probably do some style manipulation straight from JavaScript. Here&apos;s how we&apos;d pump up a balloon using the &lt;code&gt;transform&lt;/code&gt; property:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; balloon&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;.balloon&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// make the balloon bigger by 50%&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;balloon.style.transform &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &apos;scale(1.5)&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or, to make the balloon just a little bit bigger on every button click:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; pump&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;.pump&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// keep track of the balloon&apos;s size in a JS variable&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; size &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pump.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;click&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  size &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; 0.1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	balloon.style.transform &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; `scale(${&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;})`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There&apos;s nothing wrong with this so far. But it has some growing pains:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;We need to keep track of a CSS property (the balloon&apos;s &lt;code&gt;scale&lt;/code&gt; size) &lt;strong&gt;using a JS variable.&lt;/strong&gt; This could &lt;em&gt;ahem&lt;/em&gt; &lt;strong&gt;balloon&lt;/strong&gt; into a suite of state variables overtime as we animate more elements throughout our app.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;We&apos;re writing our CSS using strings.&lt;/strong&gt; This leaves a sour taste in my mouth personally, since we loose all our syntax highlighting + editor suggestions. It can also get nasty to maintain when we want that &lt;code&gt;size&lt;/code&gt; variable in other parts of our styles. For example, what if we wanted to change the &lt;code&gt;background-position&lt;/code&gt; as the balloon inflates? Or the &lt;code&gt;height&lt;/code&gt; and &lt;code&gt;width&lt;/code&gt;? Or some &lt;code&gt;linear-gradient&lt;/code&gt; with multiple color positions?&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;CSS variables to the rescue&lt;/h3&gt;&lt;p&gt;As you may have guessed, we can store this &lt;code&gt;size&lt;/code&gt; from our code as a CSS variable!&lt;/p&gt;&lt;p&gt;We can use the same &lt;code&gt;.style&lt;/code&gt; attribute as before, this time using the &lt;code&gt;setProperty&lt;/code&gt; function to assign a value:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; size &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pump.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;click&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  size &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; 0.1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	balloon.style.&lt;/span&gt;&lt;span&gt;setProperty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;--size&apos;&lt;/span&gt;&lt;span&gt;, size);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, slide that variable into our &lt;code&gt;transform&lt;/code&gt; property from the CSS:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.balloon&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* set a default / starting value if JS doesn&apos;t supply anything */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --size&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* use var(...) to apply the value */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  transform&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;scale&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--size&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Heck, you can ditch that &lt;code&gt;size&lt;/code&gt; variable entirely and make CSS the source of truth! Just read the value from CSS directly whenever you try to increment it:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pump.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;click&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // Note: you *can&apos;t* use balloon.style here!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // This won&apos;t give you the up-to-date value of your variable.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // For that, you&apos;ll need getComputedStyle(...)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; size&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getComputedStyle&lt;/span&gt;&lt;span&gt;(balloon).&lt;/span&gt;&lt;span&gt;getPropertyValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;--size&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // size is a string at this stage, so we&apos;ll need to cast it to a number&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  balloon.style.&lt;/span&gt;&lt;span&gt;setProperty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;--size&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;parseFloat&lt;/span&gt;&lt;span&gt;(size) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 0.1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There&apos;s some caveats to this of course. Namely, CSS variables &lt;strong&gt;are always strings&lt;/strong&gt; when you retrieve them, so you&apos;ll need to cast to an &lt;code&gt;int&lt;/code&gt; or a &lt;code&gt;float&lt;/code&gt; (for decimals) as necessary. The whole &lt;code&gt;.style&lt;/code&gt; vs. &lt;code&gt;getComputedStyle&lt;/code&gt; is a little weird to remember as well, so do whatever makes sense for you!&lt;/p&gt;&lt;p&gt;Here&apos;s a fully working example to &lt;em&gt;pump&lt;/em&gt; up your confidence 🎈&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/vYXogza&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;h2&gt;✨ Let&apos;s get rolling on our shiny button&lt;/h2&gt;&lt;p&gt;Before putting our newfound CSS variable knowledge to the test, let&apos;s jump into the styles we&apos;ll need for this button.&lt;/p&gt;&lt;p&gt;Remember that we want a smooth gradient of color to follow our mouse cursor, like a light shining on a piece of metal. As you can imagine, we&apos;ll want a &lt;code&gt;radial-gradient&lt;/code&gt; on our &lt;code&gt;button&lt;/code&gt; that we can easily move around.&lt;/p&gt;&lt;p&gt;We could add a gradient as a secondary background on our button (yes, you can &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Backgrounds_and_Borders/Using_multiple_backgrounds&quot;&gt;overlay multiple backgrounds&lt;/a&gt; on the same element!). But for the sake of simplicity, let&apos;s just add another element &lt;em&gt;inside&lt;/em&gt; our button representing our &quot;shiny&quot; effect. We&apos;ll do this using a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements&quot;&gt;pseudo-element&lt;/a&gt; to be fancy 😁&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.shiny-button&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* add this property to our button, */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* so we can position our shiny gradient *relative* to the button itself */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  position&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;relative&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* then, make sure our shiny effect */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* doesn&apos;t &quot;overflow&quot; outside of our button */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  overflow&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;hidden&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#3984ff&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;/* blue */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.shiny-button::after&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* all pseudo-elements need &quot;content&quot; to work. We&apos;ll make it empty here */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  position&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;absolute&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;40&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;40&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* make sure the gradient isn&apos;t too bright */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.6&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* add a circular gradient that fades out on the edges */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;radial-gradient&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;white&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;#3984ff00&lt;/span&gt;&lt;span&gt; 80&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Side note:&lt;/strong&gt; You may have noticed our 8-digit hex code on the gradient background. This is a neat feature that lets you add transparency to your hex codes! &lt;a href=&quot;https://css-tricks.com/8-digit-hex-codes/&quot;&gt;More on that here.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Great! With this in place, we should see a subtle, stationary gradient covering our button.&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/LYbPjNQ&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;h2&gt;🪤 Now, lets track some mouse cursors&lt;/h2&gt;&lt;p&gt;We&apos;ll need to dig into some native browser APIs for this. You probably just listen for &lt;code&gt;click&lt;/code&gt; 99% of the time, so it&apos;s easy to forget the dozens of other event listeners at our disposal! We&apos;ll need to use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/mousemove_event&quot;&gt;&lt;code&gt;mousemove&lt;/code&gt; event&lt;/a&gt; for our purposes:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; button&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;.shiny-button&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;button.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;mousemove&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we log out or &lt;code&gt;event&lt;/code&gt; object, we&apos;ll find some useful values in here. The main one&apos;s we&apos;re focusing on are &lt;code&gt;clientX&lt;/code&gt; and &lt;code&gt;clientY&lt;/code&gt;, which tell you the mouse position &lt;strong&gt;relative to the entire screen.&lt;/strong&gt; Hover over this button to see what those values look like:&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/yLVBNzE&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;p&gt;This is pretty useful, but it&apos;s not &lt;em&gt;quite&lt;/em&gt; the info we&apos;re looking for. Remember that our shiny effect is positioned &lt;em&gt;relative&lt;/em&gt; to the button surrounding it. For instance, to position the effect at the top-left corner of the button, we&apos;d need to set &lt;code&gt;top: 0; left: 0;&lt;/code&gt; So, we&apos;d expect a reading of &lt;code&gt;x: 0 y: 0&lt;/code&gt; when we hover in our example above... But this definitely &lt;em&gt;isn&apos;t&lt;/em&gt; the values that &lt;code&gt;clientX&lt;/code&gt; and &lt;code&gt;clientY&lt;/code&gt; give us 😕&lt;/p&gt;&lt;p&gt;There isn&apos;t a magical &lt;code&gt;event&lt;/code&gt; property for this, so we&apos;ll need to get a little creative. Remember that &lt;code&gt;clientX&lt;/code&gt; and &lt;code&gt;clientY&lt;/code&gt; give us the &lt;strong&gt;cursor position&lt;/strong&gt; relative to the window we&apos;re in. There&apos;s also this neat function called &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect&quot;&gt;&lt;code&gt;getBoundingClientRect()&lt;/code&gt;&lt;/a&gt;, which gets the x and y position of our &lt;strong&gt;button&lt;/strong&gt; relative to the window. So if we subtract our button&apos;s position from our cursor&apos;s position... we should get our position relative to the button!&lt;/p&gt;&lt;p&gt;This is probably best explored with visuals. Hover your mouse around to see how our &lt;code&gt;mouse&lt;/code&gt; values, &lt;code&gt;boundingClientRect&lt;/code&gt; values, and subtracted values all interact:&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/YzpKGbo&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;h2&gt;💅 Pipe those coordinates into CSS&lt;/h2&gt;&lt;p&gt;Alright, let&apos;s put two and two together here! We&apos;ll pass our values from the &lt;code&gt;mousemove&lt;/code&gt; listener:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;button.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;mousemove&quot;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; button.&lt;/span&gt;&lt;span&gt;getBoundingClientRect&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  button.style.&lt;/span&gt;&lt;span&gt;setProperty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;--x&quot;&lt;/span&gt;&lt;span&gt;, e.clientX &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; x);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  button.style.&lt;/span&gt;&lt;span&gt;setProperty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;--y&quot;&lt;/span&gt;&lt;span&gt;, e.clientY &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; y);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, we&apos;ll add some CSS variables to that shiny pseudo-element from before:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.shiny-button::after&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  top&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;calc&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--y&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; 50&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  left&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;calc&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--x&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; 50&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;A couple notes here:&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;We can set a default value for our variables using the second argument to &lt;code&gt;var&lt;/code&gt;. In this case, we&apos;ll use 0 for both.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;CSS variables have a weird concept of &quot;types.&quot; Here, we&apos;re assuming we&apos;ll pass our &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; as integers.  This makes sense from our JavaScript, but CSS has a hard time figuring out that something like &lt;code&gt;10&lt;/code&gt; &lt;em&gt;really&lt;/em&gt; means &lt;code&gt;10px&lt;/code&gt;. To fix this, &lt;strong&gt;just multiply by the unit you want using &lt;code&gt;calc&lt;/code&gt;&lt;/strong&gt; (aka &lt;code&gt;* 1px&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;We subtract half the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; from our positioning. This ensures that our shiny circle is centered up with our cursor, instead of following with the top left corner.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Fade into our effect on entry&lt;/h3&gt;&lt;p&gt;We&apos;re pretty much done here! Just one small tweak: if we leave this animation as-is, our shiny effect will &lt;em&gt;always&lt;/em&gt; show in some corner of our button (even when we aren&apos;t hovering).&lt;/p&gt;&lt;p&gt;We could fix this from JavaScript to show and hide the effect. But why do that when CSS lets you style-on-hover already?&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* to explain this selector, we&apos;re */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/* selecting our ::after element when the .shiny-button is :hover-ed over */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.shiny-button:hover::after&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* show a faded shiny effect on hover */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.4&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.shiny-button::after&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* ease into view when &quot;transitioning&quot; to a non-zero opacity */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  transition&lt;/span&gt;&lt;span&gt;: opacity &lt;/span&gt;&lt;span&gt;0.2&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Boom! Just add a one-line transition effect, and we get a nice fade-in. Here&apos;s our finished product ✨&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/RwobPJG&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;h2&gt;🔨 Adapt to your framework of choice&lt;/h2&gt;&lt;p&gt;I get it, you might be dismissing this article with all the &lt;code&gt;eventListeners&lt;/code&gt; thinking &lt;em&gt;well, I&apos;m sure that JS looks much different in framework X.&lt;/em&gt; Luckily, the transition is pretty smooth!&lt;/p&gt;&lt;p&gt;First, you&apos;ll need to grab a &lt;strong&gt;reference&lt;/strong&gt; to the button you&apos;re shine-ifying. In React, we can use a &lt;code&gt;useRef&lt;/code&gt; hook to retrieve this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; ShinyButton&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // null to start&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; buttonRef&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useRef&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  React.&lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // add a useEffect to check that our buttonRef has a value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (buttonRef) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [buttonRef])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; ref&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{buttonRef}&amp;gt;✨✨✨&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or in Svelte, we can &lt;code&gt;bind&lt;/code&gt; our element to a variable:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  import&lt;/span&gt;&lt;span&gt; { onMount } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;svelte&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; buttonRef&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // our ref always has a value onMount!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  onMount&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; bind:this&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{buttonRef}&lt;/span&gt;&lt;span&gt;&amp;gt;✨✨✨&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Aside: I always like including Svelte examples, since they&apos;re usually easier to understand&lt;/em&gt; 😁&lt;/p&gt;&lt;p&gt;Once we have this reference, it&apos;s business-as-usual for our property setting:&lt;/p&gt;&lt;h3&gt;React example&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; ShinyButton&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; buttonRef&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useRef&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // throw your mousemove callback up here to &quot;add&quot; and &quot;remove&quot; later&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // might be worth a useCallback based on the containerRef as well!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; mouseMoveEvent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; containerRef.current.&lt;/span&gt;&lt;span&gt;getBoundingClientRect&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    containerRef.current.style.&lt;/span&gt;&lt;span&gt;setProperty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;--x&apos;&lt;/span&gt;&lt;span&gt;, e.clientX &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; x);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    containerRef.current.style.&lt;/span&gt;&lt;span&gt;setProperty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;--y&apos;&lt;/span&gt;&lt;span&gt;, e.clientY &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; y);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  React.&lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (buttonRef) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      buttonRef.current.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;mousemove&apos;&lt;/span&gt;&lt;span&gt;, mouseMoveEvent)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // don&apos;t forget to *remove* the eventListener&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // when your component unmounts!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; buttonRef.current.&lt;/span&gt;&lt;span&gt;removeEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;mousemove&apos;&lt;/span&gt;&lt;span&gt;, mouseMoveEvent)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [buttonRef])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Svelte example&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  import&lt;/span&gt;&lt;span&gt; { onMount, onDestroy } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;svelte&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; buttonRef&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // again, declare your mousemove callback up top&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  function&lt;/span&gt;&lt;span&gt; mouseMoveEvent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; buttonRef.&lt;/span&gt;&lt;span&gt;getBoundingClientRect&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    buttonRef.style.&lt;/span&gt;&lt;span&gt;setProperty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;--x&apos;&lt;/span&gt;&lt;span&gt;, e.clientX &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; x);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    buttonRef.style.&lt;/span&gt;&lt;span&gt;setProperty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;--y&apos;&lt;/span&gt;&lt;span&gt;, e.clientY &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; y);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  onMount&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		buttonRef.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;mousemove&apos;&lt;/span&gt;&lt;span&gt;, mouseMoveEvent)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  onDestroy&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    buttonRef.&lt;/span&gt;&lt;span&gt;removeEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;mousemove&apos;&lt;/span&gt;&lt;span&gt;, mouseMoveEvent)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The main takeaway: 💡 &lt;strong&gt;don&apos;t forget to remove event listeners when your component unmounts!&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;Check out our live example on Hack4Impact&lt;/h2&gt;&lt;p&gt;If you want to see how this works in-context, check out this CodeSandbox for our Hack4Impact site. We also added some CSS fanciness to make this effect usable on &lt;em&gt;any&lt;/em&gt; element, not just buttons ✨&lt;/p&gt;&lt;p&gt;&lt;strong&gt;To check out the component, &lt;a href=&quot;https://codesandbox.io/s/hack4impact-website-0kn5m?file=/components/shared/Nav/index.tsx&quot;&gt;head over here&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;And if this helped you out, you can learn more about ❤️ &lt;a href=&quot;https://hack4impact.org/&quot;&gt;Hack4Impact here&lt;/a&gt;.&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Mocking browser APIs in Jest (localStorage, fetch and more!)</title><link>https://bholmes.dev/blog/mocking-browser-apis-fetch-localstorage-dates-the-easy-way-with-jest/</link><guid isPermaLink="true">https://bholmes.dev/blog/mocking-browser-apis-fetch-localstorage-dates-the-easy-way-with-jest/</guid><description>Turns out, Jest gives you access to all sorts of browser APIs (fetch, localStorage, etc) that you can mock at a moment&apos;s notice! Let&apos;s see how.</description><pubDate>Tue, 19 Jan 2021 22:48:29 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;I hit a snag recently trying to test a &lt;code&gt;localStorage&lt;/code&gt; helper written in React. Figuring out how to test all my state and render changes was certainly the easy part (thanks as always &lt;a href=&quot;https://github.com/testing-library/react-testing-library&quot;&gt;React Testing Library&lt;/a&gt; 🐐).&lt;/p&gt;&lt;p&gt;But soon, I found myself wondering... is there an easy way to &quot;mock&quot; a browser API like storage? Or better yet, how should I test &lt;strong&gt;any function using X API?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Well, hope you&apos;re hungry! We&apos;re gonna explore&lt;/p&gt;&lt;ul&gt;&lt;li&gt;🚅 Why dependency injection doesn&apos;t feel like a silver bullet&lt;/li&gt;&lt;li&gt;📦 How we can mock &lt;code&gt;localStorage&lt;/code&gt; using the &lt;code&gt;global&lt;/code&gt; object&lt;/li&gt;&lt;li&gt;📶 Ways to go further mocking the &lt;code&gt;fetch&lt;/code&gt; API&lt;/li&gt;&lt;li&gt;🔎 An alternative approach using &lt;code&gt;jest.spyOn&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;em&gt;Onwards!&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;Let&apos;s grab something to eat first&lt;/h2&gt;&lt;p&gt;Here&apos;s a simple (and tasty) example of a function worth testing:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; saveForLater&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;leftoverChili&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const&lt;/span&gt;&lt;span&gt; whatsInTheFridge&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; localStorage.&lt;/span&gt;&lt;span&gt;getItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;mealPrepOfTheWeek&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (whatsInTheFridge &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; undefined&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // if our fridge is empty, chili&apos;s on the menu 🌶&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     	localStorage.&lt;/span&gt;&lt;span&gt;setItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;mealPrepOfTheWeek&apos;&lt;/span&gt;&lt;span&gt;, leftoverChili) &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // otherwise, we&apos;ll just bring it to our neighbor&apos;s potluck 🍽&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      goToPotluck&lt;/span&gt;&lt;span&gt;(leftoverChili)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // if something went wrong, we&apos;re going to the potluck empty-handed 😬&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    goToPotluck&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is pretty straightforward... but it&apos;s got some &lt;code&gt;localStorage&lt;/code&gt; madness baked-in. We could probably start with the &lt;strong&gt;inject all the things strategy (TM)&lt;/strong&gt; to address this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; saveForLater&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;leftoverChili&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // treat our storage calls as parameters to the function,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // with the default value set to our desired behavior&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  getFromStorage&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; localStorage.&lt;/span&gt;&lt;span&gt;getItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;mealPrepOfTheWeek&apos;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setInStorage&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;food&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; localStorage.&lt;/span&gt;&lt;span&gt;setItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;mealPrepOfTheWeek&apos;&lt;/span&gt;&lt;span&gt;, food) &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // then, sub these values into our function&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; whatsInTheFridge&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getFromStorage&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setInStorage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;leftoverChili&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, our test file can pass some tasty mock functions we can work with:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;puts the chili in the fridge when the fridge is empty&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // just make some dummy functions, where the getter returns undefined&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; getFromStorage&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; jest.&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;mockReturnValueOnce&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // then, make a mock storage object to check&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // whether the chili was put in the fridge&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; mockStorage&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; setInStorage&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; jest.&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; { mockStorage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; value })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	saveForLater&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;chili&apos;&lt;/span&gt;&lt;span&gt;, { getFromStorage, setInStorage })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  expect&lt;/span&gt;&lt;span&gt;(setInStorage).&lt;/span&gt;&lt;span&gt;toHaveBeenCalledOnce&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  expect&lt;/span&gt;&lt;span&gt;(mockFridge).&lt;/span&gt;&lt;span&gt;toEqual&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;chili&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This isn&apos;t &lt;em&gt;too&lt;/em&gt; bad. Now we can check whether our &lt;code&gt;localStorage&lt;/code&gt; functions get called, and verify that we&apos;re sending the right values.&lt;/p&gt;&lt;p&gt;Still, there&apos;s something a bit ugly here: &lt;strong&gt;we just restructured our code for the sake of cleaner tests!&lt;/strong&gt; I don&apos;t know about you, but I feel a little uneasy moving the internals of my function to a set of &lt;em&gt;parameters&lt;/em&gt;. And what if the unit tests move away or get rewritten years down the line? That leaves us with another odd design choice to pass onto the next developer 😕&lt;/p&gt;&lt;h2&gt;📦 What if we could mock browser storage directly?&lt;/h2&gt;&lt;p&gt;Sure, &lt;a href=&quot;https://stackoverflow.com/questions/45111198/how-to-mock-functions-in-the-same-module-using-jest&quot;&gt;mocking module functions &lt;em&gt;we wrote ourselves&lt;/em&gt; is pretty tough.&lt;/a&gt; But mocking native APIs is surprisingly straightforward! Let me stir the pot a little 🥘&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// let&apos;s make a mock fridge (storage) for all our tests to use&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; mockFridge &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;beforeAll&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  global.Storage.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.setItem &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; jest.&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    mockFridge[key] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  global.Storage.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.getItem &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; jest.&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; mockFridge[key])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;beforeEach&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // make sure the fridge starts out empty for each test&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  mockFridge &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;afterAll&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // return our mocks to their original values&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 🚨 THIS IS VERY IMPORTANT to avoid polluting future tests!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	global.Storage.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.setItem.&lt;/span&gt;&lt;span&gt;mockReset&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  global.Storage.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.getItem.&lt;/span&gt;&lt;span&gt;mockReset&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Oh, take a look at that meaty piece in the middle! There&apos;s some big takeaways from this:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Jest gives you a nice &lt;code&gt;global&lt;/code&gt; object to work with.&lt;/strong&gt; More specifically, Jest gives you access to &lt;a href=&quot;https://github.com/jsdom/jsdom&quot;&gt;JSDOM&lt;/a&gt; out-of-the-box, which populates &lt;code&gt;global&lt;/code&gt; (a standard in Node) with a treasure trove of APIs. As we&apos;ve discovered, it includes our favorite browser APIs as well!&lt;/li&gt;&lt;li&gt;&lt;strong&gt;We can use the &lt;code&gt;prototype&lt;/code&gt; to mock functions inside a JS class.&lt;/strong&gt; You&apos;re right to wonder &lt;em&gt;why&lt;/em&gt; we need to mock &lt;code&gt;Storage.prototype&lt;/code&gt;, rather than mocking &lt;code&gt;localStorage&lt;/code&gt; directly. In short: &lt;code&gt;localStorage&lt;/code&gt; is actually an &lt;em&gt;instance&lt;/em&gt; of a Storage &lt;em&gt;class.&lt;/em&gt; Sadly, mocking methods on a class instance (i.e. &lt;code&gt;localStorage.getItem&lt;/code&gt;) doesn&apos;t work with our &lt;code&gt;jest.fn&lt;/code&gt; approach. But don&apos;t fret! You can &lt;a href=&quot;https://stackoverflow.com/a/41434763&quot;&gt;mock the entire &lt;code&gt;localStorage&lt;/code&gt; class&lt;/a&gt; as well if this &lt;code&gt;prototype&lt;/code&gt; madness makes you feel uneasy 😁 Fair warning though: it&apos;s a bit harder to test whether class methods were called with &lt;code&gt;toHaveBeenCalled&lt;/code&gt; compared to a plan ole&apos; &lt;code&gt;jest.fn&lt;/code&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;💡 &lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; This strategy will mock both &lt;code&gt;localStorage&lt;/code&gt; and &lt;code&gt;sessionStorage&lt;/code&gt; with the same set of functions. If you need to mock these independently, you might need to split up your test suites or &lt;a href=&quot;https://stackoverflow.com/a/41434763&quot;&gt;mock the storage class&lt;/a&gt; as previously suggested.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Now, we&apos;re good to test our original function injection-free!&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;puts the chili in the fridge when the fridge is empty&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	saveForLater&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;chili&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  expect&lt;/span&gt;&lt;span&gt;(global.Storage.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.setItem).&lt;/span&gt;&lt;span&gt;toHaveBeenCalledOnce&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  expect&lt;/span&gt;&lt;span&gt;(mockStorage[&lt;/span&gt;&lt;span&gt;&apos;mealPrepOfTheWeek&apos;&lt;/span&gt;&lt;span&gt;]).&lt;/span&gt;&lt;span&gt;toEqual&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;chili&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There&apos;s almost no setup to speak of now that we&apos;re mocking &lt;code&gt;global&lt;/code&gt; values. &lt;strong&gt;Just remember to clean the kitchen in that &lt;code&gt;afterAll&lt;/code&gt; block,&lt;/strong&gt; and we&apos;re good to go 👍&lt;/p&gt;&lt;h2&gt;📶 So what else could we mock?&lt;/h2&gt;&lt;p&gt;Now that we&apos;re cooking with crisco, let&apos;s try our hand at some more &lt;code&gt;global&lt;/code&gt; functions. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;&lt;strong&gt;The &lt;code&gt;fetch&lt;/code&gt; API&lt;/strong&gt;&lt;/a&gt; is a great candidate for this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// let&apos;s fetch some ingredients from the store&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; grabSomeIngredients&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  	const&lt;/span&gt;&lt;span&gt; res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;https://wholefoods.com/overpriced-organic-spices&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  	const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;cumin&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;paprika&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;chiliPowder&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return&lt;/span&gt;&lt;span&gt; [cumin, paprika, chiliPowder] &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Seems simple enough! We&apos;re just making sure that the Cumin, Paprika, and Chili Powder get fetched and returned in an array of chili spices 🌶&lt;/p&gt;&lt;p&gt;As you might expect, we&apos;re using the same &lt;code&gt;global&lt;/code&gt; strategy as before:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;fetches the right ingredients&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; cumin&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;McCormick ground cumin&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; paprika&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;Smoked paprika&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; chiliPowder&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;Spice Islands Chili Powder&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; spices &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; { cumin, paprika, chiliPowder, garlicSalt: &lt;/span&gt;&lt;span&gt;&apos;Yuck. Fresh garlic only!&apos;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  global.fetch &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; jest.&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;mockImplementationOnce&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      resolve&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // first, mock the &quot;json&quot; function containing our result&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        json&lt;/span&gt;&lt;span&gt;: () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          // then, resolve this second promise with our spices&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          resolve&lt;/span&gt;&lt;span&gt;(spices)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; grabSomeIngredients&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  expect&lt;/span&gt;&lt;span&gt;(res).&lt;/span&gt;&lt;span&gt;toEqual&lt;/span&gt;&lt;span&gt;([cumin, paprika, chiliPowder])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Not too bad! You&apos;ll probably need a second to process that doubly-nested &lt;code&gt;Promise&lt;/code&gt; we&apos;re mocking (remember, &lt;code&gt;fetch&lt;/code&gt; returns &lt;em&gt;another&lt;/em&gt; promise for the &lt;code&gt;json&lt;/code&gt; result!). Still, our test stayed pretty lean while thoroughly testing our function.&lt;/p&gt;&lt;p&gt;You&apos;ll also notice that we used &lt;strong&gt;&lt;code&gt;mockImplementationOnce&lt;/code&gt;&lt;/strong&gt; here. Sure, we could have used the same &lt;code&gt;beforeAll&lt;/code&gt; technique as before, but we probably want to mock different implementations for &lt;code&gt;fetch&lt;/code&gt; once we get into the error scenarios. Here&apos;s how that might look:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;it&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;returns an empty array on bad fetch&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    global.fetch &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; jest.&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;mockImplementationOnce&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;reject&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        reject&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;404&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetchSomething&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // if our fetch fails, we don&apos;t get any spices!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    expect&lt;/span&gt;&lt;span&gt;(res).&lt;/span&gt;&lt;span&gt;toEqual&lt;/span&gt;&lt;span&gt;([])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  it&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;returns an empty array on bad json format&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    global.fetch &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; jest.&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;mockImplementationOnce&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        resolve&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          json&lt;/span&gt;&lt;span&gt;: () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;reject&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; reject&lt;/span&gt;&lt;span&gt;(error)),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetchSomething&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    expect&lt;/span&gt;&lt;span&gt;(res).&lt;/span&gt;&lt;span&gt;toEqual&lt;/span&gt;&lt;span&gt;([])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And since we&apos;re mocking implementation &lt;em&gt;once,&lt;/em&gt; there&apos;s no &lt;code&gt;afterAll&lt;/code&gt; cleanup to worry about! Pays to clean your dishes as soon as you&apos;re done with them 🧽&lt;/p&gt;&lt;h2&gt;🔎 Addendum: using &quot;spies&quot;&lt;/h2&gt;&lt;p&gt;Before wrapping up, I want to point out an alternative approach: mocking &lt;code&gt;global&lt;/code&gt; using &lt;a href=&quot;https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname&quot;&gt;Jest spies&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Let&apos;s refactor our &lt;code&gt;localStorage&lt;/code&gt; example from before:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// first, we&apos;ll need to make some variables to hold onto our spies&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// we&apos;ll use these for clean-up later&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; setItemSpy, getItemSpy&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;beforeAll&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // previously: global.Storage.prototype.setItem = jest.fn(...)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	setItemSpy &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; jest&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;spyOn&lt;/span&gt;&lt;span&gt;(global.Storage.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;setItem&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;mockImplementation&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      mockStorage[key] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // previously: global.Storage.prototype.getItem = jest.fn(...)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  getItemSpy &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; jest&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;spyOn&lt;/span&gt;&lt;span&gt;(global.Storage.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;getItem&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;mockImplementation&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; mockStorage[key])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;afterAll&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // then, detach our spies to avoid breaking other test suites&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  getItemSpy.&lt;/span&gt;&lt;span&gt;mockRestore&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setItemSpy.&lt;/span&gt;&lt;span&gt;mockRestore&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Overall, this is pretty much identical to our original approach. The only difference is in the semantics; instead of &lt;strong&gt;assigning new behavior&lt;/strong&gt; to these global functions (i.e. &lt;code&gt;= jest.fn()&lt;/code&gt;), we&apos;re &lt;strong&gt;intercepting requests to these functions&lt;/strong&gt; and using our own implementation.&lt;/p&gt;&lt;p&gt;This might feel a little &quot;safer&quot; to some people, since we&apos;re not explicitly overwriting the behavior of these functions anymore. But as long as you pay attention to your cleanup in the &lt;code&gt;afterAll&lt;/code&gt; block, either approach is valid 😁&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Leaving Notion to build a second brain in VS Code</title><link>https://bholmes.dev/blog/giving-up-on-notion-to-build-a-second-brain/</link><guid isPermaLink="true">https://bholmes.dev/blog/giving-up-on-notion-to-build-a-second-brain/</guid><description>Why I&apos;m ditching my favorite notetaking app to get organized, write everything down, and build a second brain.</description><pubDate>Mon, 11 Jan 2021 22:34:17 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;Okay, I&apos;ll be honest: I have a serious problem with notetaking apps. I&apos;ve whipped through every service imaginable for a few months each... but I&apos;ve never truly felt &quot;at home.&quot;&lt;/p&gt;&lt;p&gt;I think this is my problem above all: &lt;strong&gt;I need to write something down, but I don&apos;t know what magical folder / page / service / tag to stuff it into.&lt;/strong&gt; So, I just choose the first note app I find on whatever device I&apos;m using. I swear to myself I&apos;ll move it to the right place &lt;em&gt;someday,&lt;/em&gt; but the thought of organizing everything is too overwhelming to take action 😬&lt;/p&gt;&lt;p&gt;So, let&apos;s talk about:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;🙅‍♂️ Why Notion isn&apos;t cutting it for me&lt;/li&gt;&lt;li&gt;📜 What the second brain / Zettelkasten method is&lt;/li&gt;&lt;li&gt;🕺 How Foam + VS Code helped me find a groove with personal notetaking&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Onwards!&lt;/p&gt;&lt;h2&gt;Why not Notion?&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://notion.so/&quot;&gt;Notion&lt;/a&gt; has quickly become the go-to recommendation for personal notes and journals. There&apos;s definitely a lot to fall in love with from a developer&apos;s perspective:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Easy to whip up code snippets with syntax highlighting&lt;/li&gt;&lt;li&gt;Easy export to markdown&lt;/li&gt;&lt;li&gt;Tons of integration options for &lt;em&gt;data tables&lt;/em&gt;, if you&apos;re into that kind of thing&lt;/li&gt;&lt;li&gt;Keyboard shortcuts galore&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;etc etc etc. But from what I&apos;ve found, getting into Notion is a lot like getting into Vim. &lt;strong&gt;You spend 30% of your time in efficiency heaven, and another 70% setting up the tool itself!&lt;/strong&gt; As a UI designer and developer myself, I just can&apos;t deal with all these knobs and switches. It&apos;s so tempting to tweak everything to perfection... and once the dust has settled, I still can&apos;t find that code snippet I made a week ago 😔&lt;/p&gt;&lt;p&gt;I also don&apos;t see any updates helping me from this perspective. My problem is really the fundamental approach of Notion: let the user build &lt;em&gt;their own&lt;/em&gt; UI with as much flexibility as possible. I don&apos;t really need all this flexibility if I&apos;m just journaling my thoughts and todos.&lt;/p&gt;&lt;h3&gt;Ignoring the bells and whistles doesn&apos;t help much either&lt;/h3&gt;&lt;p&gt;Yes of course, I can just cast away my temptations and stick to basic note pages. But this still doesn&apos;t address other elephants in the room.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;First, fuzzy finding notes isn&apos;t the easiest.&lt;/strong&gt; Notion did add a &quot;quick find&quot; option that helps, but dang, the load times are still pretty slow 😢 Just trying to find a page &lt;em&gt;by its name,&lt;/em&gt; I&apos;ve waited a few beats before finding the result I&apos;m looking for (or no results at all). I expect this to improve overtime of course. But since the data is owned by &lt;em&gt;Notion&lt;/em&gt; instead of your &lt;em&gt;personal machine,&lt;/em&gt; there&apos;s always going to be loading spinners.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;I also can&apos;t shake the niceties of VS Code.&lt;/strong&gt; Right now, Notion doesn&apos;t offer options to globally find using regex, search by &quot;directory&quot; (or parent pages in Notion I suppose), or other useful tools developers take for granted. And let&apos;s not forget the shortcuts while you&apos;re editing! Find-and-replace, or multi-cursors, quickly bumping lines higher or lower? Truly a text editor&apos;s greatest hits.&lt;/p&gt;&lt;h2&gt;My dream workflow&lt;/h2&gt;&lt;p&gt;With all that aside, let&apos;s get to the heart of what I &lt;em&gt;really&lt;/em&gt; want:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Go from zero to writing a note with no effort.&lt;/strong&gt; Ideally, I just whip up a new file without worrying &lt;em&gt;where&lt;/em&gt; it goes&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Easily find my notes later.&lt;/strong&gt; Fuzzy find, tags, file names... whatever it takes.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Keep the UI simple!&lt;/strong&gt; A vast majority of the time, markdown syntax is all I need to get my ideas down.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Turns out, there&apos;s an entire notetaking strategy that addresses these concerns!&lt;/p&gt;&lt;h3&gt;Enter the second brain 🧠&lt;/h3&gt;&lt;p&gt;I&apos;d been hearing about this concept for a while now, but never realized it&apos;s an actual &lt;em&gt;strategy&lt;/em&gt; to notetaking. As you&apos;d expect, it&apos;s all about writing as much as possible so you can, well, have a second brain in &lt;strong&gt;note form.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;This concept starts with the &lt;a href=&quot;https://zettelkasten.de/introduction/&quot;&gt;Zettelkasten Method&lt;/a&gt; used back in the pen-and-paper ages. It&apos;s built on some pretty basic principles:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Every note is treated as a &lt;em&gt;unique&lt;/em&gt; collection of thoughts, tagged by a unique ID of some sort&lt;/li&gt;&lt;li&gt;Notes should form an &lt;strong&gt;ever-sprawling tree&lt;/strong&gt; of connected ideas / thoughts. This is pulled off with &quot;links&quot; between notes (references to those unique IDs), much like hyperlinks on the web!&lt;/li&gt;&lt;li&gt;You may index multiple &quot;trees&quot; of notes with tags or tables of contents, assuming your Zettelkasten grows pretty large&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;There&apos;s countless nuggets of advice on how to do a Zettelkasten &lt;em&gt;right&lt;/em&gt;. But overall, it&apos;s clear that a physical Zettelkasten &lt;strong&gt;maps perfectly to how the web works.&lt;/strong&gt; So, why not use a bunch of HTML files to create one? Or better yet, markdown files?&lt;/p&gt;&lt;h2&gt;Let&apos;s get back to basics with VS Code + Foam&lt;/h2&gt;&lt;p&gt;If we&apos;re gonna simplify our notetaking process, there&apos;s nothing simpler than a text editor 😁&lt;/p&gt;&lt;p&gt;I discovered a project called &lt;a href=&quot;https://foambubble.github.io/foam/&quot;&gt;Foam&lt;/a&gt; recently that&apos;s... not really a stand-alone project; it&apos;s a collection of extensions that work well together, with some &lt;a href=&quot;https://foambubble.github.io/foam/recipes/recipes&quot;&gt;helpful guides&lt;/a&gt; on how to make the most of them.&lt;/p&gt;&lt;p&gt;All you need to do is &lt;a href=&quot;https://github.com/foambubble/foam-template&quot;&gt;clone a repository&lt;/a&gt; and watch the magic happen! It&apos;ll recommend all the extensions you should need to edit, link, and view your notes. But at the end of the day, you&apos;re really just writing a bunch of markdown files on your computer. Let&apos;s understand how powerful this can really be:&lt;/p&gt;&lt;h3&gt;Getting a birds-eye view 🗺&lt;/h3&gt;&lt;p&gt;It&apos;s worth discussing a crucial part of the Foam style of notetaking: &lt;strong&gt;you never need to group notes by directory.&lt;/strong&gt; We&apos;re so used to using file systems to organize everything, but let&apos;s be honest, that&apos;s not how our brain works!&lt;/p&gt;&lt;p&gt;Foam thrives on connecting notes with &lt;em&gt;links&lt;/em&gt;, rather than folder hierarchies. This makes it much easier to define notes that might be referenced in a ton of places. Instead of finding the &lt;em&gt;exact&lt;/em&gt; directory where a note lives, you just need to reference the file itself.&lt;/p&gt;&lt;p&gt;Foam will help you find any patterns that naturally emerge from links with a &lt;a href=&quot;https://foambubble.github.io/foam/features/graph-visualisation&quot;&gt;graph visualization extension&lt;/a&gt;. It&apos;s basically one big &lt;a href=&quot;https://www.youtube.com/watch?v=jPCuQMKVh0Y&quot;&gt;map of your head&lt;/a&gt; that you can click into and explore!&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2F1-foam-graph.B1W5Tcqs.png&quot; alt=&quot;Connected web of notes on the Rust Lang book&quot; width=&quot;1400&quot; height=&quot;570&quot; /&gt;&lt;/p&gt;&lt;p&gt;This is the graph generated by my &lt;a href=&quot;https://twitter.com/BHolmesDev/status/1329265837828677632&quot;&gt;recent challenge to learn Rust lang&lt;/a&gt;. Notice how this doesn&apos;t &lt;em&gt;quite&lt;/em&gt; match up to a parent-child relationship that directory trees demand. For instance, &quot;Intro to structs&quot; on the far left gets referenced by &quot;Enums&quot; &lt;em&gt;and&lt;/em&gt; &quot;Rust ownership.&quot; But you couldn&apos;t have the same file sitting in multiple directories at once! That&apos;s the beauty of using free-form links; &lt;strong&gt;anything can reference anything else&lt;/strong&gt;, so it&apos;s less of a &lt;em&gt;tree&lt;/em&gt; and more of a purposeful, tangled-up birds nest.&lt;/p&gt;&lt;h3&gt;Solving the &quot;I need to write this ASAP&quot; problem&lt;/h3&gt;&lt;p&gt;This beautiful graph solves our organization paralysis from earlier! Now that we&apos;ve ditched the whole directory paradigm, there&apos;s a much lower barrier to entry when writing down something new.&lt;/p&gt;&lt;p&gt;And if we happen to write a note that doesn&apos;t reference any other notes, no worries! Our graph visualization will help us find those stragglers later on. This helped me consolidate some stray notes on books and podcasts into a table of contents like so:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# The Logs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;This is where I&apos;ll record everything I consume that &lt;/span&gt;&lt;span&gt;_taught me something._&lt;/span&gt;&lt;span&gt; Topic? Doesn&apos;t matter. Length of the summary? Could be a couple words. Just need to record everything I possibly can!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## Podcasts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **11-28-20**&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span&gt;writing-for-software-developers-with-Philip-Kiely&lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## Blog posts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **12-20-20**&lt;/span&gt;&lt;span&gt; [[Kent-C-Dodds-Avoiding-re-renders a-useDebounce-example.md]]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;## Books&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **11-26-20**&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span&gt;the-dark-forest-Cixin-Liu&lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; **12-29-20**&lt;/span&gt;&lt;span&gt; [[&lt;/span&gt;&lt;span&gt;the-case-against-education-bryan-caplan&lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Side note:&lt;/strong&gt; If you haven&apos;t read the &lt;a href=&quot;https://www.amazon.com/Three-Body-Problem-Cixin-Liu/dp/0765382032/ref=sr_1_1?crid=3TMIORQZ73Q6E&amp;amp;dchild=1&amp;amp;keywords=three+body+problem&amp;amp;qid=1609545547&amp;amp;sprefix=three+body+%2Caps%2C202&amp;amp;sr=8-1&quot;&gt;Three Body Problem&lt;/a&gt;, do it. It&apos;s unlike anything I&apos;ve seen before.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;This fixes a painpoint I found using Notion as well. Though it&apos;s great for quickly linking pages from other pages, you can never get a high-level overview of how everything fits together. Now, all your notes can fit together like puzzle pieces 🧩&lt;/p&gt;&lt;h3&gt;Other niceties&lt;/h3&gt;&lt;p&gt;Foam seems to have a solid community of contributors adding new features. For example, there&apos;s an easy way to discover all the pages referencing the current page you&apos;re own using &lt;strong&gt;backlinks.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2F2-backlinks.oqXzLXbL.png&quot; alt=&quot;VS code extension that lists all pages referencing my current page&quot; width=&quot;600&quot; height=&quot;362&quot; /&gt;&lt;/p&gt;&lt;p&gt;This shows all the references for that &quot;Intro to Structs&quot; note I mentioned earlier, right from the VS Code toolbar. It&apos;s a nice lay-of-the-land without scanning the entire graph visualization yourself.&lt;/p&gt;&lt;p&gt;There&apos;s also a way to add &lt;strong&gt;tags&lt;/strong&gt; to your notes to group common topics. All you need is a little metadata at the top of your note:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;tags: [&lt;/span&gt;&lt;span&gt;learning-rust&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And boom! It&apos;s globally discoverable in VS Code as well 💪&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2F3-tag-explorer.cgjaVaiH.png&quot; alt=&quot;VS toolbar readout of all notes using a given tag&quot; width=&quot;522&quot; height=&quot;266&quot; /&gt;&lt;/p&gt;&lt;h2&gt;So is this the silver bullet to notetaking?&lt;/h2&gt;&lt;p&gt;To be honest... I don&apos;t think so. At least not for &lt;em&gt;all&lt;/em&gt; of my notetaking across devices. This is due to some pretty fundamental limitations:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;There&apos;s no practical way to view these notes on a phone&lt;/strong&gt;, let alone edit them&lt;/li&gt;&lt;li&gt;&lt;strong&gt;I can&apos;t add handwritten notes and drawing from my tablet&lt;/strong&gt;, unless I want to upload screenshots everytime&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Notion had these issues too, so it&apos;s definitely not a step back for me (yes I know Notion has a mobile app, but it&apos;s full of loading spinners and far too many buttons for my tastes 😬).&lt;/p&gt;&lt;p&gt;Still, I&apos;m seriously impressed with how much I can accomplish with just a text editor and a couple extensions. It&apos;s also comforting to actually &lt;em&gt;own&lt;/em&gt; my notes instead of using a cloud-based system.&lt;/p&gt;&lt;p&gt;So, I&apos;ll definitely be sticking with Foam as I enter 2021 🎉 If you have any tips on getting more out of a &quot;second brain,&quot; please leave them in the comments on the &lt;a href=&quot;https://dev.to/bholmesdev/giving-up-on-notion-to-build-a-second-brain-8b2&quot;&gt;DEV version&lt;/a&gt; of this post.&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>The web dev tools that helped me get s*** done in 2020</title><link>https://bholmes.dev/blog/the-web-dev-tools-that-helped-me-get-s-done-in-2020/</link><guid isPermaLink="true">https://bholmes.dev/blog/the-web-dev-tools-that-helped-me-get-s-done-in-2020/</guid><description>From the burning ashes of 2020, I&apos;ve unearthed some gems that really improved my life as a dev. Let&apos;s talk notes, git snippets, state machines, and FP!</description><pubDate>Mon, 04 Jan 2021 16:33:47 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;At long last... the great dumpster fire that was 2020 is no more. There&apos;s certainly &lt;em&gt;a lot&lt;/em&gt; I&apos;d like to leave behind us. But like a phoenix rising from the ashes, I want to highlight some amazing tools and practices that actually made my life &lt;strong&gt;better&lt;/strong&gt; as a web developer 🚀&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://bholmes.dev/blog/the-web-dev-tools-that-helped-me-get-s-done-in-2019-plus-a-thank-you-11fe&quot;&gt;&lt;strong&gt;I wrote a similar post back in 2019&lt;/strong&gt;&lt;/a&gt; once I really found my footing in the web dev space. Now, I&apos;m gonna make this a yearly tradition! 🥳&lt;/p&gt;&lt;p&gt;Let&apos;s jump into:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;🌳 &lt;strong&gt;4 Git snippets that defined my workflow&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;🧠 &lt;strong&gt;My mission to build a second brain in VS Code&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;⚙️ &lt;strong&gt;Discovering that &lt;em&gt;everything&lt;/em&gt; is a state machine with XState&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;🐏 &lt;strong&gt;The joys of functional programming in JS using Ramda&lt;/strong&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;em&gt;Onwards!&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;4 Git snippets I use daily&lt;/h2&gt;&lt;p&gt;I started my first full-time job as a programmer this year, which means I&apos;ve been picking up &lt;em&gt;a lot&lt;/em&gt; of new tricks in a pretty quick timeframe. Naturally, I started automating the workflows I use day-in and day-out 😁&lt;/p&gt;&lt;p&gt;Stop me if you&apos;ve seen this workflow before:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;I picked up a ticket on JIRA&lt;/li&gt;&lt;li&gt;I need to pull the latest main branch&lt;/li&gt;&lt;li&gt;I need to checkout a new branch&lt;/li&gt;&lt;li&gt;I need to push that branch to origin to and collaborate with my team and open my PR&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I&apos;ll probably do this 5+ times in one day if we&apos;re in a bug-squashing flow. But when I&apos;m hurrying, it&apos;s so easy to either &lt;strong&gt;a)&lt;/strong&gt; work off an old &quot;main&quot; branch, or &lt;strong&gt;b)&lt;/strong&gt; do the &lt;strong&gt;copy-paste of shame&lt;/strong&gt; before your PR:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;To push the current branch and set the remote as upstream, use&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    git push --set-upstream origin crap-not-again&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;You know you cringe a little every time this pops up&lt;/em&gt; 😬&lt;/p&gt;&lt;p&gt;To mitigate this, I&apos;ve created my &lt;strong&gt;fantastic 4&lt;/strong&gt; shortcuts for slamming through my daily tasks 💪&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Stash what I&apos;m working on and checkout the latest master&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;alias&lt;/span&gt;&lt;span&gt; gimme&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;git stash push -u &amp;amp;&amp;amp; git checkout master &amp;amp;&amp;amp; git pull -r&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Grab the latest master and come back to the branch I was on (with stashing!)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;alias&lt;/span&gt;&lt;span&gt; yoink&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;gimme &amp;amp;&amp;amp; git checkout - &amp;amp;&amp;amp; git stash pop&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Checkout a new branch and push it to origin (so I don&apos;t forget that set-upstream)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;woosh&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  git&lt;/span&gt;&lt;span&gt; checkout&lt;/span&gt;&lt;span&gt; -b&lt;/span&gt;&lt;span&gt; $1&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; push&lt;/span&gt;&lt;span&gt; -u&lt;/span&gt;&lt;span&gt; origin&lt;/span&gt;&lt;span&gt; HEAD&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# ALL TOGETHER NOW&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Stash my current WIP, checkout a new branch off the latest master, and push it to origin&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;alias&lt;/span&gt;&lt;span&gt; boop&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;gimme &amp;amp;&amp;amp; woosh&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&apos;s understand what these commands are doing a little more:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;gimme:&lt;/code&gt;&lt;/strong&gt; This command assumes you might &lt;em&gt;not&lt;/em&gt; be on the main branch just yet. It will first stash anything you&apos;re working on, including &quot;untracked&quot; / new files with the &lt;code&gt;-u&lt;/code&gt; flag. Then, it will head over to the main branch and pull the latest. That &lt;code&gt;-r&lt;/code&gt; flag will be sure to &quot;rebase&quot; onto the latest, preventing unecessary merge conflicts.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;yoink:&lt;/code&gt;&lt;/strong&gt; This builds off of &lt;code&gt;gimme&lt;/code&gt; a little bit. Instead of remaining on the main branch when we&apos;re done, we&apos;ll head &lt;em&gt;back&lt;/em&gt; to the branch we were just on using the &lt;code&gt;-&lt;/code&gt; parameter. This is super convenient for grabbing the latest changes to &lt;a href=&quot;https://egghead.io/lessons/git-push-a-rebased-local-branch-by-using-force-with-lease&quot;&gt;force rebase the branch&lt;/a&gt; we&apos;re working on.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;woosh:&lt;/code&gt;&lt;/strong&gt; This fixes our &lt;code&gt;--set-upstream&lt;/code&gt; problem from earlier. Instead of pushing to origin later, this lets you check out a new branch and push &lt;em&gt;right away.&lt;/em&gt; Yes, there&apos;s some cases when you don&apos;t want your local branch on the remote, but this is pretty rare in my experience. And if you&apos;ve never seen the &lt;code&gt;HEAD&lt;/code&gt; parameter before... &lt;strong&gt;remember that one!&lt;/strong&gt; It&apos;s a super slick way to auto-fill the name of your current branch instead of typing it out by hand 🔥&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;boop:&lt;/code&gt;&lt;/strong&gt; This command goes full-circle. It&apos;ll stash your current work, grab the latest main branch, &lt;em&gt;and&lt;/em&gt; push it up to origin before you start working. This is the command I use the most!&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Also gonna mention &lt;a href=&quot;https://www.jason.af/&quot;&gt;Jason Lengstorf&lt;/a&gt; (&lt;em&gt;the lord of boops&lt;/em&gt;) for the naming convention here 😁&lt;/p&gt;&lt;h2&gt;Building a second brain in VS Code&lt;/h2&gt;&lt;p&gt;This year was all about &lt;em&gt;writing, writing and &lt;strong&gt;more&lt;/strong&gt; writing&lt;/em&gt; for me. There&apos;s just so much knowledge to pick up as a new full stack developer, yet it always feels so difficult to find what I wrote even a week ago!&lt;/p&gt;&lt;p&gt;I&apos;ve struggled from notetaking app fatigue for years now. But at long last... I think I&apos;ve found a semi-perfect home for everything I write both on &lt;em&gt;and&lt;/em&gt; off the clock.&lt;/p&gt;&lt;h3&gt;Enter the second brain 🧠&lt;/h3&gt;&lt;p&gt;I&apos;d been hearing about this concept for a while now, but never realized it&apos;s an actual &lt;em&gt;strategy&lt;/em&gt; to notetaking. As you&apos;d expect, it&apos;s all about writing as much as possible, so you can build a brain&apos;s worth of knowledge in &lt;strong&gt;note form.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;This concept starts with the &lt;a href=&quot;https://zettelkasten.de/introduction/&quot;&gt;Zettelkasten Method&lt;/a&gt; used back in the pen-and-paper ages. It&apos;s built on some pretty basic principles:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Every note is treated as a &lt;em&gt;unique&lt;/em&gt; collection of thoughts, tagged by a unique ID of some sort&lt;/li&gt;&lt;li&gt;Notes should form an &lt;strong&gt;ever-sprawling tree&lt;/strong&gt; of connected ideas / thoughts. This is pulled off with &quot;links&quot; between notes (references to those unique IDs), much like hyperlinks on the web!&lt;/li&gt;&lt;li&gt;You may index multiple &quot;trees&quot; of notes with tags or tables of contents, assuming your Zettelkasten grows pretty large&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;There&apos;s countless nuggets of advice on how to do a Zettelkasten &lt;em&gt;right&lt;/em&gt;. But overall, it&apos;s clear that a physical Zettelkasten &lt;strong&gt;maps perfectly to how the web works.&lt;/strong&gt; So, why not use a bunch of HTML files to create one? Or better yet, markdown files?&lt;/p&gt;&lt;h3&gt;Using VS Code + Foam&lt;/h3&gt;&lt;p&gt;I discovered a project called &lt;a href=&quot;https://foambubble.github.io/foam/&quot;&gt;Foam&lt;/a&gt; recently that&apos;s... not really a stand-alone project; it&apos;s a collection of extensions that work well together, with some &lt;a href=&quot;https://foambubble.github.io/foam/recipes/recipes&quot;&gt;helpful guides&lt;/a&gt; on how to make the most of them.&lt;/p&gt;&lt;p&gt;All you need to do is &lt;a href=&quot;https://github.com/foambubble/foam-template&quot;&gt;clone a repository&lt;/a&gt; and watch the magic happen! It&apos;ll recommend all the extensions you should need to edit, link, and view your notes. But at the end of the day, you&apos;re really just writing a bunch of markdown files on your computer + some added benefits.&lt;/p&gt;&lt;h4&gt;Getting a birds-eye view 🗺&lt;/h4&gt;&lt;p&gt;It&apos;s worth discussing a crucial part of the Foam style of notetaking: &lt;strong&gt;you never need to group notes by directory.&lt;/strong&gt; We&apos;re so used to using file systems to organize everything, but let&apos;s be honest, that&apos;s not how our brain works!&lt;/p&gt;&lt;p&gt;Foam thrives on connecting notes with &lt;em&gt;links&lt;/em&gt;, rather than folder hierarchies. This makes it much easier to define notes that might be referenced in a ton of places. Instead of finding the &lt;em&gt;exact&lt;/em&gt; directory where a note should live, you just need to reference the file itself.&lt;/p&gt;&lt;p&gt;Foam will help you find any patterns that naturally emerge from links with a &lt;a href=&quot;https://foambubble.github.io/foam/features/graph-visualisation&quot;&gt;graph visualization extension&lt;/a&gt;. It&apos;s basically one big &lt;a href=&quot;https://www.youtube.com/watch?v=jPCuQMKVh0Y&quot;&gt;map of your head&lt;/a&gt; that you can click into and explore!&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2F1-foam-graph.B1W5Tcqs.png&quot; alt=&quot;Connected web of notes on the Rust Lang book&quot; width=&quot;1400&quot; height=&quot;570&quot; /&gt;&lt;/p&gt;&lt;p&gt;This is the graph generated by my &lt;a href=&quot;https://twitter.com/BHolmesDev/status/1329265837828677632&quot;&gt;recent challenge to learn Rust lang&lt;/a&gt;. Notice how this doesn&apos;t &lt;em&gt;quite&lt;/em&gt; match up to a parent-child relationship that directory trees demand. For instance, &quot;Intro to structs&quot; on the far left gets referenced by &quot;Enums&quot; &lt;em&gt;and&lt;/em&gt; &quot;Rust ownership.&quot; But you couldn&apos;t have the same file sitting in multiple directories at once! That&apos;s the beauty of using free-form links; &lt;strong&gt;anything can reference anything else&lt;/strong&gt;, so it&apos;s less of a &lt;em&gt;tree&lt;/em&gt; and more of a purposeful, tangled-up birds nest 😁&lt;/p&gt;&lt;p&gt;&lt;em&gt;Metaphor for my brain&lt;/em&gt;&lt;/p&gt;&lt;p&gt;If you use VS Code every day like me, this is super easy to dive into for the first time. Definitely give it shot if &lt;em&gt;blog more&lt;/em&gt; is one of your New Year&apos;s resolutions 🎉&lt;/p&gt;&lt;h2&gt;Everything is a state machine&lt;/h2&gt;&lt;p&gt;You&apos;re a state machine. I&apos;m a state machine. &lt;em&gt;The world is a state machine.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Feverything-is-a-state-machine.B_WhuqdB.png&quot; alt=&quot;Meme with two astronauts looking at the earth. One asks &amp;quot;wait, it&apos;s all state machines?&amp;quot; and the other replies, gun in hand, &amp;quot;always has been.&amp;quot;&quot; width=&quot;600&quot; height=&quot;338&quot; /&gt;&lt;/p&gt;&lt;p&gt;...okay jokes aside, state machines really do apply to everything 😆&lt;/p&gt;&lt;p&gt;You may have heard of &lt;a href=&quot;https://xstate.js.org/&quot;&gt;XState&lt;/a&gt; as a solution to &quot;global state management&quot; in your JS apps. This is true, but the idea of state machines &lt;strong&gt;is more of a &lt;em&gt;way of thinking&lt;/em&gt; than &lt;em&gt;just another library to learn.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;h3&gt;Visualizing your state&lt;/h3&gt;&lt;p&gt;Diagramming is really XState&apos;s bread and butter. With most state management tools, the actual progression from one action / state / reducer to another can be pretty tough to trace. In XState, &lt;a href=&quot;https://xstate.js.org/viz/&quot;&gt;they have a dedicated sandbox to work from!&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Let&apos;s break down some important XState lingo here:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Finite states&lt;/strong&gt; are those words surrounded by a rectangle (idle, loading, etc.). If you come from Redux like me, you can look at each of these like mini-reducers. They each have unique actions that they&apos;re listening for, which may cause you to progress from one reducer to the next.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Actions&lt;/strong&gt; are defined by those gray bubbles between our finite states (FETCH, RESOLVE, etc.). These can do a few interesting things: progress you to another finite state, set some &quot;global&quot; state for the whole machine (see the next bullet), or fire off some &quot;side effects&quot; that do things &lt;em&gt;outside&lt;/em&gt; the machine. For instance, the &lt;code&gt;FETCH&lt;/code&gt; action might kick off an API call of some sort. If that API call comes back successfully, we&apos;ll fire the &lt;code&gt;RESOLVE&lt;/code&gt; action (which our &quot;loading&quot; state picks up).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Context&lt;/strong&gt; is some global state shared by &lt;em&gt;all&lt;/em&gt; states in the machine. This is just one big JS object you can assign new values to and read from whenever you want. In this case, we have a count of how many times we &quot;retried&quot; our fetch, which gets updated on the &lt;code&gt;RETRY&lt;/code&gt; action with &lt;strong&gt;&lt;code&gt;do / assign retries&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This approach has &lt;em&gt;countless&lt;/em&gt; benefits, but the biggest one for me: &lt;strong&gt;you can use any framework you want with XState, or even no framework at all!&lt;/strong&gt; This makes me super comfortable going all-in on the library since I can take it with me wherever I go 😁&lt;/p&gt;&lt;p&gt;To get your feet wet in this brave new world, I found some high quality demos around the Internet worth checking out:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=hiT4Q1ntvzg&quot;&gt;This one&lt;/a&gt; on modeling UI with state machines across frameworks.&lt;/strong&gt; Best conference talk on the subject hands down.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=czi24DqUfSA&quot;&gt;This one&lt;/a&gt; on building a more complex ReactJS form.&lt;/strong&gt; It&apos;s longer, but worth your time!&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=osaszd2aU9U&quot;&gt;This one&lt;/a&gt; on creating a Vanilla JS drag-and-drop interaction.&lt;/strong&gt; This is more CSS-intensive and speaks to the &lt;code&gt;className&lt;/code&gt; trick I showed above.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;One-line state machines with TypeScript&lt;/h3&gt;&lt;p&gt;I started using this pattern more and more throughout the year. This actually &lt;em&gt;doesn&apos;t&lt;/em&gt; use any libraries at all! In short, it&apos;s neat shorthand to collapse an ever-growing sea of booleans:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;formIdle&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setFormIdle&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;formInvalid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setFormInvalid&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;formSubmitting&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setFormSubmitting&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;formSubmitted&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setFormSubmitted&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...into a single, type-safe state:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; FormState&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;idle&apos;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &apos;invalid&apos;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &apos;submitting&apos;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &apos;submitted&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;formState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setFormState&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;FormState&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;&quot;idle&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This fixes the age-old problem tools like &lt;a href=&quot;https://hackernoon.com/handling-loading-actions-the-proper-way-in-redux-t3k36e8&quot;&gt;Redux seemed to encourage&lt;/a&gt;: you have a bunch of booleans representing unique states, &lt;strong&gt;but only one boolean should be flipped &quot;on&quot; at a given time.&lt;/strong&gt; It&apos;s a hassle making sure all your boolean&apos;s are &lt;code&gt;false&lt;/code&gt; when they&apos;re supposed to be, so why not have a boolean with more than 2 states?&lt;/p&gt;&lt;p&gt;There are a number of other benefits to this approach like flipping CSS classes at the right times. &lt;strong&gt;&lt;a href=&quot;https://dev.to/bholmesdev/writing-a-state-machine-in-one-line-with-typescript-3h9d&quot;&gt;Check out this article&lt;/a&gt;&lt;/strong&gt; for some interactive code examples ✨&lt;/p&gt;&lt;h2&gt;Functional programming with RamdaJS&lt;/h2&gt;&lt;p&gt;Across the tech stack my full-time job demands, &lt;a href=&quot;https://flaviocopes.com/javascript-functional-programming/#higher-order-functions&quot;&gt;functional programming&lt;/a&gt; has been &lt;em&gt;the&lt;/em&gt; common thread for me to pick up.&lt;/p&gt;&lt;p&gt;The functional programming community has flourished with all the static type checking goodness that TypeScript brought. Heck, there&apos;s an &lt;strong&gt;&lt;a href=&quot;https://github.com/gcanti/fp-ts&quot;&gt;entire helper library&lt;/a&gt;&lt;/strong&gt; for nearly every concept in the &lt;a href=&quot;https://www.youtube.com/playlist?list=PLhgq-BqyZ7i5lOqOqqRiS0U5SwTmPpHQ5&quot;&gt;category theory&lt;/a&gt; book!&lt;/p&gt;&lt;p&gt;My knowledge of true, mathematical FP is still very surface level right now. Still, I&apos;ve definitely found my &quot;gateway drug&quot; into this new world: &lt;strong&gt;I need to transform a big blob of data into a different looking blob of data, and I&apos;m gonna chain 10 functions together to do it&lt;/strong&gt; 😎&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://ramdajs.com/docs/&quot;&gt;RamdaJS&lt;/a&gt;&lt;/strong&gt; really is the missing toolkit JS needs for these acrobatics. I could ramble about each little function in their every-sprawling docs, but let&apos;s hit the biggest highlights.&lt;/p&gt;&lt;h3&gt;Boolean operations&lt;/h3&gt;&lt;p&gt;First off, Ramda throws you some nice shortcuts for combining multiple arrays. Let&apos;s consider some basic inventory management for our coffee shop. Before someone can check out, we need to exclude any items that are out of stock. Normally, we&apos;d write some function like this one:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; orders&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot;Macchiatto&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Cold brew&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Latte&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; outOfStockItems&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot;Macchiatto&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; validOrders&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; orders.&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;order&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;outOfStockItems.&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(order))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This works well enough... but it&apos;s not super readable. Watch us rewrite this sort of operation in a single stroke:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// subtract out all the outOfStockItems from orders&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; validOrders&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; difference&lt;/span&gt;&lt;span&gt;(orders, outOfStockItems)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// -&amp;gt; [&quot;Cold brew&quot;, &quot;Latte&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can do all kinds of things from here! For instance, we could filter out multiple arrays at once by &lt;code&gt;union&lt;/code&gt;ing them together:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; validOrders&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; difference&lt;/span&gt;&lt;span&gt;(orders, &lt;/span&gt;&lt;span&gt;union&lt;/span&gt;&lt;span&gt;(outOfStockItems, itemsOutForDelivery))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...or figure out which items we &lt;em&gt;should&lt;/em&gt; include using a SQL-inspired &lt;code&gt;innerJoin&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// basically, filter our menuItems based on which ones were ordered&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; itemsOrdered&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; innerJoin&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;order&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; item.name &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; order, menuItems, validOrders)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Boolean operations like these are certainly not a unique concept. Still, I&apos;m glad Ramda includes all of them with a &lt;a href=&quot;https://ramdajs.com/repl?v=0.27.0&quot;&gt;nice REPL playground&lt;/a&gt; to boot. If it&apos;s your first time seeing boolean operations, &lt;strong&gt;I highly recommend &lt;a href=&quot;https://boolean.method.ac/&quot;&gt;this interactive tutorial&lt;/a&gt;&lt;/strong&gt; 🔥&lt;/p&gt;&lt;h3&gt;Formatting in bulk with &lt;code&gt;evolve&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Okay, this helper really blew my socks off. I&apos;m sure you&apos;ve run into this sort of pattern before when you try to transform one object into another one:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; orders&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  name: &lt;/span&gt;&lt;span&gt;&quot;Macchiatto&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  options: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		roomForCream: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    cream: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      quantity: &lt;/span&gt;&lt;span&gt;&quot;1tbsp&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// now, we want to loop over all of these and convert that &quot;quantity&quot; to an int.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;orders.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;order&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; ({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // make sure the name sticks around&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	...&lt;/span&gt;&lt;span&gt;order,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  options: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // and roomForCream&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		...&lt;/span&gt;&lt;span&gt;order.options,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    cream: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // ...and finally, parse quantity to an int&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      quantity: &lt;/span&gt;&lt;span&gt;parseInt&lt;/span&gt;&lt;span&gt;(order.options.cream.quantity)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Not bad, but all that dot chaining is making my head spin. We also have to remember all of the keys at each level so they don&apos;t disappear on us. The spread operator cleaned up this process for us, but it&apos;s still easy to forget (especially if you don&apos;t have type checking!). If only we could &lt;em&gt;just&lt;/em&gt; modify the keys we care about, without all the nested dots.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;This is exactly with &lt;code&gt;evolve&lt;/code&gt; does!&lt;/strong&gt; 🎉&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;orders.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	evolve&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  	// ignore the properties we *don&apos;t* want to change, like the name&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    options: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // ignore roomForCream too&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      cream: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // and just put a reference to the parseInt function as-is. Don&apos;t even call it!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        quantity: parseInt&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There&apos;s some serious benefits to this approach. Not only can we omit keys we don&apos;t want to change, but we can modify deeply nested fields without much trouble! You&apos;ll also notice we can just pass &lt;code&gt;evolve&lt;/code&gt; into our map directly, without passing in the &lt;code&gt;order&lt;/code&gt; like we&apos;re used to doing (i.e. &lt;code&gt;map(order =&amp;gt; evolve(order))&lt;/code&gt;). This is because all Ramda functions are &lt;strong&gt;curried&lt;/strong&gt;, which you can learn a bit more about from &lt;a href=&quot;https://stackoverflow.com/questions/36314/what-is-currying&quot;&gt;this Stack Overflow answer&lt;/a&gt; 😁 Spoiler: it&apos;s an awesome feature.&lt;/p&gt;&lt;h2&gt;And that&apos;s a wrap!&lt;/h2&gt;&lt;p&gt;I know 2020 hit a lot of people hard this year. If you&apos;re struggling with family, dealing with kids at home, finding a new job, losing your mind indoors... well, at least you made it through to today 🙂&lt;/p&gt;&lt;p&gt;If you have anything else that made your life better as a developer or as a person, please drop them in the comments on the &lt;a href=&quot;https://dev.to/bholmesdev/the-web-dev-tools-that-helped-me-get-s-done-in-2020-gpl&quot;&gt;DEV version&lt;/a&gt; of this post. Would love to hear some fresh perspectives as we all take a fresh start for 2021!&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>NextJS, Contentful CMS, GraphQL, oh my!</title><link>https://bholmes.dev/blog/nextjs-contentful-cms-graphql-oh-my/</link><guid isPermaLink="true">https://bholmes.dev/blog/nextjs-contentful-cms-graphql-oh-my/</guid><description>Let&apos;s pull CMS data the easy way with GraphQL. We&apos;ll explore NextJS, Contentful, and GraphiQL from a beginner&apos;s perspective!</description><pubDate>Wed, 04 Nov 2020 22:42:55 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;We built the new &lt;a href=&quot;https://hack4impact.org/&quot;&gt;Hack4Impact.org&lt;/a&gt; in a month-long sprint once we had designs in hand. To move this fast, we needed to make sure that we used tools that played to our strengths, while setting us up for success when designers and product managers want to update our copy. As the title &lt;em&gt;excitedly&lt;/em&gt; alludes to, NextJS + Contentful + GraphQL was the match for us!&lt;/p&gt;&lt;p&gt;No, this post won&apos;t help you answer &lt;em&gt;what tools should I use to build our site&apos;s landing page?&lt;/em&gt; But it should get your gears turning on:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;How to access Contentful&apos;s GraphQL endpoints (yes, they&apos;re free-to-use now!) 📝&lt;/li&gt;&lt;li&gt;How to talk to GraphQL server + debug with GraphiQL 📶&lt;/li&gt;&lt;li&gt;How we can roll query results into a static NextJS site with &lt;code&gt;getStaticProps&lt;/code&gt; 🗞&lt;/li&gt;&lt;li&gt;Going further with rich text 🚀&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Onwards!&lt;/p&gt;&lt;h2&gt;Wait, why use these tools?&lt;/h2&gt;&lt;p&gt;Some readers might be scoping whether to adopt these tools at all. As a TLDR:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://nextjs.org/&quot;&gt;&lt;strong&gt;NextJS&lt;/strong&gt;&lt;/a&gt; was a great match for our frontend stack, since we were already comfortable with a React-based workflow and wanted to play to our strengths. What&apos;s more, NextJS is flexible enough to build some parts of your website &lt;em&gt;statically&lt;/em&gt;, and other parts &lt;em&gt;dynamically&lt;/em&gt; (i.e. with &lt;a href=&quot;https://medium.com/walmartglobaltech/the-benefits-of-server-side-rendering-over-client-side-rendering-5d07ff2cefe8&quot;&gt;serverside rendering&lt;/a&gt;). This is pretty promising as &lt;a href=&quot;https://hack4impact.org/&quot;&gt;our landing site&lt;/a&gt; expands, where we might add experiences that vary by user going forward (admin portals, nonprofit dashboards, etc).&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.contentful.com/&quot;&gt;&lt;strong&gt;Contentful&lt;/strong&gt;&lt;/a&gt; is one of the more popular &quot;headless CMSs&quot; right now, and it&apos;s easy to see why. Content types are more than flexible enough for our use cases, and the UI is friendly enough for designers and product managers to navigate confidently. It thrives with &quot;structured content&quot; in particular which is great for static sites like ours! Still, if you&apos;re looking for a simplified, key-value store for your copy, there are some &lt;a href=&quot;https://www.sanity.io/&quot;&gt;shiny alternatives&lt;/a&gt; to consider.&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://graphql.org/&quot;&gt;&lt;strong&gt;GraphQL&lt;/strong&gt;&lt;/a&gt; is the &lt;em&gt;perfect&lt;/em&gt; pairing for a CMS in our opinion. You simply define the &quot;shape&quot; of the content you want (with necessary filtering and sorting), and the CMS responds with the associated values. We&apos;ll dive into some code samples soon, but it&apos;s &lt;em&gt;much&lt;/em&gt; simpler than a traditional REST endpoint.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; There&apos;s roughly 10 billion ways to build a static site these days (citation needed), with another &lt;a href=&quot;https://www.netlify.com/blog/&quot;&gt;10 billion blog posts&lt;/a&gt; on how to tackle the problem. So don&apos;t take these reasons as prescriptive for all teams!&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;Setting up our Contentful environment&lt;/h2&gt;&lt;p&gt;Let&apos;s open up Contentful first. If you&apos;re 100% new to the platform, Contentful documents a lot of &lt;a href=&quot;https://www.contentful.com/developers/docs/concepts/&quot;&gt;core concepts over here&lt;/a&gt; to get up to speed on &quot;entries&quot; and &quot;content models.&quot;&lt;/p&gt;&lt;p&gt;When you&apos;re feeling comfortable, whip up a new workspace and create a new content model of your choosing. We&apos;ll use our &quot;Executive Board Member&quot; model as an example here.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fcontentful-model.C1DREbgI.png&quot; alt=&quot;Contentful model example&quot; width=&quot;1400&quot; height=&quot;871&quot; /&gt;&lt;/p&gt;&lt;p&gt;Once you&apos;ve saved this model, go and make some content entries in the &quot;Content&quot; panel. We&apos;ll be pulling these down with GraphQL later, so I recommend making more than 1 entry to demo sorting and filtering! You can filter by your content type for a sanity check:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fcontentful-entries.CeMjp6Gc.png&quot; alt=&quot;Contentful entry example, filtered by &amp;quot;Executive Board Member&amp;quot;&quot; width=&quot;1400&quot; height=&quot;827&quot; /&gt;&lt;/p&gt;&lt;p&gt;Before moving on, let&apos;s get some API keys for our website to use. Just head to &quot;Settings &amp;gt; API keys&quot; and choose &quot;Add API key&quot; in the top right. This should allow you to find two important variables: a &lt;strong&gt;Space ID&lt;/strong&gt; and a &lt;strong&gt;Content Delivery API access token.&lt;/strong&gt; You&apos;ll need these for some important environment variables in your local repo.&lt;/p&gt;&lt;h2&gt;Whipping up a basic NextJS site&lt;/h2&gt;&lt;p&gt;If you already have a Next project to work off of, great! Go &lt;code&gt;cd&lt;/code&gt; into that thing now. Otherwise, it&apos;s super easy to make a NextJS project from scratch using their &lt;code&gt;npx&lt;/code&gt; command:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; create-next-app&lt;/span&gt;&lt;span&gt; dope-contentful-example&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;💡 &lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; You can optionally include the &lt;code&gt;--use-npm&lt;/code&gt; flag if you want to ditch &lt;a href=&quot;https://classic.yarnpkg.com/en/&quot;&gt;Yarn&lt;/a&gt;. By default, Next will set up your project with Yarn if you have it installed globally. &lt;a href=&quot;https://www.youtube.com/watch?v=5cDLZqe735k&quot;&gt;It&apos;s your prerogative&lt;/a&gt; though!&lt;/em&gt;&lt;/p&gt;&lt;p&gt;You may have found a &quot;NextJS + Contentful&quot; example in the Next docs as well. &lt;strong&gt;Don&apos;t install that one!&lt;/strong&gt; We&apos;ll be using GraphQL for this demo, which has a slightly different setup.&lt;/p&gt;&lt;p&gt;Now, just &lt;code&gt;cd&lt;/code&gt; into your new NextJS project and create a &lt;code&gt;.env&lt;/code&gt; file with the following info:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;NEXT_PUBLIC_CONTENTFUL_SPACE_ID&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;[Your&lt;/span&gt;&lt;span&gt; Space&lt;/span&gt;&lt;span&gt; ID&lt;/span&gt;&lt;span&gt; from&lt;/span&gt;&lt;span&gt; Contentful]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;[Your&lt;/span&gt;&lt;span&gt; Content&lt;/span&gt;&lt;span&gt; Delivery&lt;/span&gt;&lt;span&gt; API&lt;/span&gt;&lt;span&gt; access&lt;/span&gt;&lt;span&gt; token&lt;/span&gt;&lt;span&gt; from&lt;/span&gt;&lt;span&gt; Contentful]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Just fill these in with your API keys and you&apos;re good to go! Yes, &lt;strong&gt;the &lt;code&gt;NEXT_PUBLIC&lt;/code&gt; prefix is necessary for these to work.&lt;/strong&gt; It&apos;s a little verbose, but it allows Next to pick up your keys without the hassle of setting up, say, &lt;a href=&quot;https://github.com/motdotla/dotenv&quot;&gt;dotenv&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;Fetching some GraphQL data&lt;/h2&gt;&lt;p&gt;Alright, so we&apos;ve set the stage. &lt;em&gt;Now let&apos;s fetch our data!&lt;/em&gt;&lt;/p&gt;&lt;p&gt;We&apos;ll be using GraphiQL to view our &quot;schemas&quot; with a nice GUI. &lt;a href=&quot;https://www.electronjs.org/apps/graphiql&quot;&gt;&lt;strong&gt;You can install this tool here&lt;/strong&gt;&lt;/a&gt;, using either homebrew on MacOS or the &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/wsl/install-win10&quot;&gt;Linux Subsystem&lt;/a&gt; on Windows. Otherwise, if you want to follow along as a &lt;code&gt;curl&lt;/code&gt; or Postman warrior, be my guest!&lt;/p&gt;&lt;p&gt;Opening the app for the first time, you should see a screen like this:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fgraphiql-welcome.15DK40g6.png&quot; alt=&quot;GraphiQL welcome screen&quot; width=&quot;1400&quot; height=&quot;1035&quot; /&gt;&lt;/p&gt;&lt;p&gt;Let&apos;s point GraphiQL to our Contentful server. You can start by entering the following URL, filling in &lt;strong&gt;[Space ID]&lt;/strong&gt; with your API key from the previous section:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://graphql.contentful.com/content/v1/spaces/[Space&lt;/span&gt;&lt;span&gt; ID]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you try to hit the play button ▶️ after this step, you should get an authorizaton error. That&apos;s because we haven&apos;t passed an access token with our query!&lt;/p&gt;&lt;p&gt;To fix this, click &lt;strong&gt;Edit HTTP Headers&lt;/strong&gt; and create a new header entry like so, filling in &lt;strong&gt;[Contentful access token]&lt;/strong&gt; with the value from your API keys:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fgraphiql-headers.wUcFefdK.png&quot; alt=&quot;Edit HTTP headers screen. Make sure Header name = &amp;quot;authorizaton&amp;quot; and Header value = &amp;quot;Bearer [Contentful access token]&amp;quot;&quot; width=&quot;1400&quot; height=&quot;1035&quot; /&gt;&lt;/p&gt;&lt;p&gt;After saving, you should see some info appear in your &quot;Documentation Explorer.&quot; If you click on the &lt;strong&gt;query: Query&lt;/strong&gt; link, you&apos;ll see an overview of all your Content models from Contentful.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fgraphiql-docs.CY72werM.png&quot; alt=&quot;Schema explorer overview&quot; width=&quot;1400&quot; height=&quot;1035&quot; /&gt;&lt;/p&gt;&lt;p&gt;Neat! From here, you should see all of the content models you created in your Contentful space. You&apos;ll notice there&apos;s a distinction between individual entries and a &quot;collection&quot; (i.e. &lt;code&gt;executiveBoardMember&lt;/code&gt; vs. &lt;code&gt;executiveBoardMemberCollection&lt;/code&gt;). This is because each represents a different &lt;strong&gt;query&lt;/strong&gt; you can perform in your API call. If this terminology confuses you, here&apos;s a quick breakdown:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;items highlighted in &lt;strong&gt;blue&lt;/strong&gt; represent &lt;strong&gt;queries&lt;/strong&gt; you can perform. These are similar to REST endpoints, as they accept parameters and return a structured response. The main difference is being able to &lt;em&gt;nest queries within other queries&lt;/em&gt; to retrieve nested content. We&apos;ll explore this concept through example.&lt;/li&gt;&lt;li&gt;items highlighted in &lt;strong&gt;purple&lt;/strong&gt; represent &lt;strong&gt;parameters&lt;/strong&gt; you can pass for a given query. As shown in the screenshot above, you can query for an individual &lt;code&gt;ExecutiveBoardMember&lt;/code&gt; based on &lt;code&gt;id&lt;/code&gt; or &lt;code&gt;locale&lt;/code&gt; (we&apos;ll ignore the &lt;code&gt;preview&lt;/code&gt; param for this tutorial), or query for a collection / list of members (&lt;code&gt;ExecutiveBoardMemberCollection&lt;/code&gt;) filtering by &lt;code&gt;locale&lt;/code&gt;, amount of entries (&lt;code&gt;limit&lt;/code&gt;), sort &lt;code&gt;order&lt;/code&gt;, and a number of other properties.&lt;/li&gt;&lt;li&gt;items highlighted in &lt;strong&gt;yellow&lt;/strong&gt; represent &lt;strong&gt;the shape of the response&lt;/strong&gt; you receive from a given query. This allows you to pull out the &lt;em&gt;exact&lt;/em&gt; keys of a given content entry that you want, with type checking built-in. Each of these are hyperlinks, so click on them to inspect the nested queries and response types!&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Getting our hands dirty&lt;/h3&gt;&lt;p&gt;Let&apos;s jump into an example. First, let&apos;s just get the list of names and emails for all &quot;Executive Board Member&quot; entries. If you&apos;re following along with your own Contentful space, just pick a few text-based keys you want to retrieve from your content model. Since we&apos;re looking for multiple entries, we&apos;ll use the &lt;code&gt;executiveBoardMemberCollection&lt;/code&gt; query for this.&lt;/p&gt;&lt;p&gt;Clicking into the yellow &lt;code&gt;ExecutiveBoardMemberCollection&lt;/code&gt; link (following the colon : at the end of the query), we should see a few options we&apos;re free to retrieve: &lt;strong&gt;total, skip, limit, and items.&lt;/strong&gt; You&apos;ll see these 4 queries on every collection you create, where &lt;strong&gt;items&lt;/strong&gt; represents the actual list of items you&apos;re hoping to retrieve. Let&apos;s click into the response type for &lt;strong&gt;items&lt;/strong&gt; to see the shape of our content:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fgraphiql-exec-member-entry.Dq89kOp_.png&quot; alt=&quot;Schema for our Executive Board Member content model&quot; width=&quot;1400&quot; height=&quot;1035&quot; /&gt;&lt;/p&gt;&lt;p&gt;This looks really similar to the content model we wrote in Contentful! As you can see, we can query for any of these fields to retrieve a response (most of them being strings in this example).&lt;/p&gt;&lt;h3&gt;Writing your first query&lt;/h3&gt;&lt;p&gt;Alright, we&apos;ve walked through the docs and found the queries we want... so how do we get that data?&lt;/p&gt;&lt;p&gt;Well, the recap, here&apos;s the basic skeleton of info we need to retrieve:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;executiveBoardMemberCollection -&amp;gt; query for a collection of entries&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  items -&amp;gt; retrieve the list items&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    name -&amp;gt; retrieve the name for each list item&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    email -&amp;gt; and the email&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can convert this skeleton to the JSON-y syntax GraphQL expects:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  executiveBoardMemberCollection {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    items {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      name&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      email&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;... and enter this into GraphiQL&apos;s textbox and hit play ▶️&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fgraphiql-response.xp89O4e-.png&quot; alt=&quot;Entering our query into GraphiQL, with response data appearing on the right&quot; width=&quot;1400&quot; height=&quot;1035&quot; /&gt;&lt;/p&gt;&lt;p&gt;Boom! There&apos;s all the data we entered into Contentful, formatted as a nice JSON response. If you typed your query into GraphiQL by hand, you may have noticed some nifty autocomplete as you went. This is the beauty of GraphQL: since we know the shape of any possible response, it can autofill your query as you go! 🚀&lt;/p&gt;&lt;h3&gt;Applying filters&lt;/h3&gt;&lt;p&gt;You may have noticed some purple items in parenthesis while exploring the docs. These are parameters we can pass to Contentful to further refine our results.&lt;/p&gt;&lt;p&gt;Let&apos;s use some of the &lt;code&gt;collection&lt;/code&gt; filters as an example; say we only want to retrieve board members that have a LinkedIn profile. We can apply this filter using the &lt;strong&gt;where&lt;/strong&gt; parameter:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fgraphiql-filter.DVvth14x.png&quot; alt=&quot;Applying a filter using &amp;quot;where.&amp;quot; Shows how GraphiQL autocomplete will reveal potential parameter values.&quot; width=&quot;1400&quot; height=&quot;1035&quot; /&gt;&lt;/p&gt;&lt;p&gt;As you can see, &lt;code&gt;where&lt;/code&gt; accepts an object as a value, where we can apply a set of pre-determined filters from Contentful. As we type, we&apos;re greated with a number of comparison options that might remind you of SQL, including the &lt;strong&gt;exists&lt;/strong&gt; operator for nullable values. You can find the complete list of supported filters in the docs off to the right, but autocomplete will usually take you to the filter you want 💪&lt;/p&gt;&lt;p&gt;In our case, our query should look something like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;executiveBoardMemberCollection(where: {linkedIn_exists: true}) { ... }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...and our result should filter out members without a LinkedIn entry.&lt;/p&gt;&lt;h2&gt;Pulling our data into NextJS&lt;/h2&gt;&lt;p&gt;Alright, we figured out how to retrieve our data. All we need is an API call on our NextJS site and we&apos;re off to the races 🏎&lt;/p&gt;&lt;p&gt;Let&apos;s pop open a random page component in our &lt;code&gt;/pages&lt;/code&gt; directory and add a call to &lt;code&gt;getStaticProps()&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// pages/about&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getStaticProps&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    props: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // our beautiful Contentful content&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you&apos;re unfamiliar with Next, this function allows you to pull in data &lt;em&gt;while your app is getting built,&lt;/em&gt; so you access that data in your component&apos;s &lt;code&gt;props&lt;/code&gt; at runtime. This means you &lt;em&gt;don&apos;t have to call Contentful&lt;/em&gt; when your component mounts! The data is just... there, ready for you to use 👍&lt;/p&gt;&lt;p&gt;💡 &lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; There&apos;s a distinction between getting these props &quot;on every page request&quot; versus retrieval &quot;at build time.&quot; For a full rundown of the difference, &lt;a href=&quot;https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering&quot;&gt;check out the NextJS docs&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Inside this function, we&apos;ll make a simple call to Contentful using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;fetch&lt;/a&gt; (but feel free to use axios if that&apos;s more your speed):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getStaticProps&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // first, grab our Contentful keys from the .env file&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; space&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; process.env.&lt;/span&gt;&lt;span&gt;NEXT_PUBLIC_CONTENTFUL_SPACE_ID&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; accessToken&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; process.env.&lt;/span&gt;&lt;span&gt;NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // then, send a request to Contentful (using the same URL from GraphiQL)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      `https://graphql.contentful.com/content/v1/spaces/${&lt;/span&gt;&lt;span&gt;space&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        method: &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// GraphQL *always* uses POST requests!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        headers: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &apos;content-type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          authorization: &lt;/span&gt;&lt;span&gt;`Bearer ${&lt;/span&gt;&lt;span&gt;accessToken&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// add our access token header&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // send the query we wrote in GraphiQL as a string&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        body: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          // all requests start with &quot;query: &quot;, so we&apos;ll stringify that for convenience&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          query: &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            executiveBoardMemberCollection {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              items {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                name&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                email&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;              }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	// grab the data from our response&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const { data } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Woah, that&apos;s a lot going on! In the end, we&apos;re just re-writing the logic that GraphiQL does under the hood. Some key takeaways:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;We need to grab our API keys for the URL and authorization header.&lt;/strong&gt; This should look super familiar after our GraphiQL setup!&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Every GraphQL query should be a POST request.&lt;/strong&gt; This is because we&apos;re sending a &lt;code&gt;body&lt;/code&gt; field to Contentful, containing the &quot;shape&quot; of the content we want to receive.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Our query should start with the JSON key &lt;code&gt;{ &quot;query&quot;: &quot;string&quot; }&lt;/code&gt;.&lt;/strong&gt; To make this easier to type, we create a JavaScript object starting with &quot;query&quot; and convert this to a string.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;🏁 As a checkpoint, add a &lt;code&gt;console.log&lt;/code&gt; statement to see what our &lt;code&gt;data&lt;/code&gt; object looks like. If all goes well, you should get a collection of contentful entries!&lt;/p&gt;&lt;p&gt;Now, we just need to return the data we want as props (in this case, the items in our &lt;code&gt;executiveBoardMemberCollection&lt;/code&gt;):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getStaticProps&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    props: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	execBoardMembers: data.executiveBoardMemberCollection.items,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...and do something with those props in our page component:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; AboutPage&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;execBoardMembers&lt;/span&gt;&lt;span&gt; }) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	{execBoardMembers.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;execBoardMember&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      	&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;exec-member-profile&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        	&amp;lt;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;{execBoardMember.name}&amp;lt;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;{execBoardMember.email}&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ))}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; AboutPage;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hopefully, you&apos;ll see your own Contentful entries pop onto the page 🎉&lt;/p&gt;&lt;h3&gt;Writing a reusable helper functions&lt;/h3&gt;&lt;p&gt;This all works great, but it gets pretty repetitive generating this API call on every page. That&apos;s why we wrote a little helper function on our project to streamline the process.&lt;/p&gt;&lt;p&gt;In short, we&apos;re gonna move all our API call logic into a utility function, accepting the actual &quot;body&quot; of our query as a parameter. Here&apos;s how that could look:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// utils/contentful&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; space&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; process.env.&lt;/span&gt;&lt;span&gt;NEXT_PUBLIC_CONTENTFUL_SPACE_ID&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; accessToken&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; process.env.&lt;/span&gt;&lt;span&gt;NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; fetchContent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // add a try / catch loop for nicer error handling&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      `https://graphql.contentful.com/content/v1/spaces/${&lt;/span&gt;&lt;span&gt;space&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        method: &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        headers: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &apos;content-type&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          authorization: &lt;/span&gt;&lt;span&gt;`Bearer ${&lt;/span&gt;&lt;span&gt;accessToken&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // throw our query (a string) into the body directly&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        body: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ query }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; res.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; data;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // add a descriptive error message first,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // so we know which GraphQL query caused the issue&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`There was a problem retrieving entries with the query ${&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(error);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Aside from our little catch statement for errors, this is the same fetch call we were making before. Now, we can refactor our &lt;code&gt;getStaticProps&lt;/code&gt; function to something like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { fetchContent } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@utils/contentful&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; getStaticProps&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fetchContent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			executiveBoardMemberCollection {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				items {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    			name&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    			email&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  `&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    props: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      execBoardMembers: response.executiveBoardMemberCollection.items,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...and we&apos;re ready to make Contentful queries across the site ✨&lt;/p&gt;&lt;h3&gt;Aside: use the &quot;@&quot; as a shortcut to directories&lt;/h3&gt;&lt;p&gt;You may have noticed that &lt;code&gt;import&lt;/code&gt; statement in the above example, accessing &lt;code&gt;fetchContent&lt;/code&gt; from &lt;code&gt;@utils/contentful&lt;/code&gt;. This is using a slick webpack shortcut under the hood, which you can set up too! Just create a &lt;code&gt;next.config.json&lt;/code&gt; that looks like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;compilerOptions&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;baseUrl&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;./&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;paths&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;@utils/*&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;utils/*&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;@components/*&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;components/*&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, you can reference anything inside &lt;code&gt;/utils&lt;/code&gt; using this decorator. For convenience, we added an entry for &lt;code&gt;@components&lt;/code&gt; as well, since NextJS projects tend to pull from that directory a lot 👍&lt;/p&gt;&lt;h3&gt;Using a special Contentful package to format rich text&lt;/h3&gt;&lt;p&gt;Chances are, you&apos;ll probably set up some rich text fields in Contentful to handle hyperlinks, headers, and the like. By default, Contentful will return a big JSON blob representing your formatted content:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://bholmes.dev/.netlify/images?url=_astro%2Fgraphiql-rich-text.DiIP7Bsf.png&quot; alt=&quot;GraphiQL response when querying for rich text&quot; width=&quot;1400&quot; height=&quot;1035&quot; /&gt;&lt;/p&gt;&lt;p&gt;...which isn&apos;t super useful on its own. To convert this into some nice HTML, you&apos;ll need a &lt;a href=&quot;https://www.npmjs.com/package/@contentful/rich-text-html-renderer&quot;&gt;special package from Contentful&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span&gt; --save-dev&lt;/span&gt;&lt;span&gt; @contentful/rich-text-html-renderer&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will take in the JSON object and (safely) render HTML for your component:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { documentToHtmlString } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@contentful/rich-text-html-renderer&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; AboutPage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;execBoardMember&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    dangerouslySetInnerHTML&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    __html: &lt;/span&gt;&lt;span&gt;documentToHtmlString&lt;/span&gt;&lt;span&gt;(execBoardMember.description.json),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }}&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Yes, using &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt; is pretty tedious. We&apos;d suggest making a &lt;code&gt;RichText&lt;/code&gt; component to render your HTML.&lt;/p&gt;&lt;h2&gt;Check out our project to see how we put it together 🚀&lt;/h2&gt;&lt;p&gt;If you&apos;re interested, we deployed our entire project to a CodeSandbox for you to explore!&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://codesandbox.io/s/hack4impact-website-0kn5m?file=/pages/about/index.tsx&quot;&gt;Head over here&lt;/a&gt;&lt;/strong&gt; to see how we retrieve our executive board members on &lt;a href=&quot;https://hack4impact.org/about&quot;&gt;our about page&lt;/a&gt;. Also, check out the &lt;code&gt;utils/contentful&lt;/code&gt; directory to see how we defined our schemas using TypeScript.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/hack4impact/hack4impact-website&quot;&gt;Our repo&lt;/a&gt; is 100% open as well, so give it a ⭐️ if this article helped you!&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Understanding the spectrum of CSS frameworks</title><link>https://bholmes.dev/blog/understanding-the-spectrum-of-css-frameworks/</link><guid isPermaLink="true">https://bholmes.dev/blog/understanding-the-spectrum-of-css-frameworks/</guid><description>We&apos;ve come a long way from Bootstrap. Let&apos;s explore the CSS framework spectrum from utility classes to all-in-one component powerhouses.</description><pubDate>Wed, 21 Oct 2020 22:44:21 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;I saw a &lt;a href=&quot;https://dev.to/urielbitton/stop-using-css-frameworks-3gpc&quot;&gt;short think piece&lt;/a&gt; by @urielbitton recently on why people should &lt;strong&gt;&quot;stop using CSS frameworks.&quot;&lt;/strong&gt; It was meant to start developer discussion more than anything, since there are definitely merits to rolling your own design system from scratch. Still, with a title like that, it sparked... &lt;em&gt;quite&lt;/em&gt; the uproar in the comments 😆&lt;/p&gt;&lt;p&gt;I &lt;a href=&quot;https://dev.to/bholmesdev/comment/16one&quot;&gt;posted my own response&lt;/a&gt; on some of the nuances to explore before &lt;em&gt;writing off frameworks for good.&lt;/em&gt; A kind commenter suggested it be its own post (thanks for the support y&apos;all!), so without further ado...&lt;/p&gt;&lt;p&gt;&lt;em&gt;Here&apos;s my clap back&lt;/em&gt; 👏&lt;/p&gt;&lt;h2&gt;Understanding the spectrum of CSS frameworks&lt;/h2&gt;&lt;p&gt;First, I&apos;d want to draw a distinction between&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Pre-styled, prescriptive, all-in-one frameworks like &lt;a href=&quot;https://material-ui.com/&quot;&gt;Material UI&lt;/a&gt; and &lt;a href=&quot;https://getbootstrap.com/&quot;&gt;Bootstrap&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Hands-off, unopinionated, utility frameworks like &lt;a href=&quot;https://reach.tech/&quot;&gt;Reach UI&lt;/a&gt; and &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind CSS&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I&apos;d consider both camps to be &quot;CSS frameworks,&quot; but for different audiences and different use cases.&lt;/p&gt;&lt;h2&gt;1. The case for all-in-one frameworks&lt;/h2&gt;&lt;p&gt;⭐️ This category is all about developer &lt;em&gt;guidance&lt;/em&gt;, giving you the building blocks you need for powerful web apps (even if you don&apos;t have the domain knowledge). Above all, these are important for developers that need to focus on the business logic and product vision, but &lt;em&gt;don&apos;t&lt;/em&gt; need hyper-bespoke CSS to get there.&lt;/p&gt;&lt;h3&gt;Accessible by default&lt;/h3&gt;&lt;p&gt;The training wheels enforced by these frameworks ensure accessibility guidelines are met without as much research on the developer&apos;s part. You may not think about it, but &lt;strong&gt;hacking together dropdowns and tab sliders that &lt;em&gt;look&lt;/em&gt; good won&apos;t always work well &lt;em&gt;for all people.&lt;/em&gt;&lt;/strong&gt; I recently wrote a longform post on &lt;a href=&quot;https://dev.to/hack4impact/building-a-sexy-mobile-ready-navbar-in-any-web-framework-3lm2&quot;&gt;building an accessible navbar&lt;/a&gt; for this very reason; there&apos;s so many little things to consider that Material UI et al. will consider ahead-of-time!&lt;/p&gt;&lt;p&gt;Given how mature popular CSS frameworks have become, they have a component for basically every accessibility concern you can think of. This is a huge win for teams without the resources to audit and fix a11y issues.&lt;/p&gt;&lt;h3&gt;Inoffensive visual identity&lt;/h3&gt;&lt;p&gt;These sorts of frameworks also enforce a &lt;strong&gt;visual identity&lt;/strong&gt; out of the box. Yes, this does cause websites built on Material UI, Bootstrap, etc. to feel a bit cookie-cutter. Still, these are sensible defaults to build from, &lt;em&gt;especially&lt;/em&gt; for &lt;strong&gt;a)&lt;/strong&gt; developer teams lacking a design department and &lt;strong&gt;b)&lt;/strong&gt; internal tools that don&apos;t need the customization effort.&lt;/p&gt;&lt;p&gt;Plus, there are gray areas in frameworks like Bootstrap that &lt;em&gt;do&lt;/em&gt; allow you to build a visual identity on top of their tools. Heck, I &lt;a href=&quot;https://www.impactlabs.io/&quot;&gt;just worked on a project&lt;/a&gt; in this camp! We just used Bootstrap for the 12 column grid (which my team was most comfortable with using), and themed the default styles into oblivion.&lt;/p&gt;&lt;p&gt;That said, I do agree with a point raised by Uriel on this front:&lt;/p&gt;&lt;p&gt;&lt;em&gt;&quot;Having custom styles is complicated (overriding issues) and will require multiple CSS files to be loaded.&quot;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;For full-on component libraries like Material UI, it is pretty difficult to customize beyond colors and fonts. You can quickly fall into the weeds of excessive prop passing, mismatched styling across your site, and the &lt;em&gt;infamous&lt;/em&gt; &lt;code&gt;!important&lt;/code&gt; bonanza 😞&lt;/p&gt;&lt;p&gt;This is why I&apos;d suggest solutions like Material UI only if your &quot;visual identity&quot; won&apos;t diverge from the defaults too much. If you find yourself outgrowing the walled garden though, there&apos;s always camp #2...&lt;/p&gt;&lt;h2&gt;2. The case for utility frameworks&lt;/h2&gt;&lt;p&gt;⭐️ This category is all about developer &lt;em&gt;expediency,&lt;/em&gt; providing helpful abstractions for common use cases without prescribing a look and feel to work from. These systems offer a lot more freedom by being, well, a lot smaller out of the box!&lt;/p&gt;&lt;p&gt;&quot;Utility&quot; is a very generic label that could apply to anything in UI-land. For simplicity, I&apos;ll just explore 2 major movements I&apos;ve seen emerging: class-driven frameworks, and functionality-first frameworks.&lt;/p&gt;&lt;h3&gt;CSS class utilities - Tailwind and Bulma&lt;/h3&gt;&lt;p&gt;Frameworks like &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind&lt;/a&gt; are really shaking things up. It&apos;s the first framework I&apos;ve seen to go all-in on class-based styling. In other words, why can&apos;t &lt;em&gt;every&lt;/em&gt; CSS property be its own class in your HTML?&lt;/p&gt;&lt;p&gt;At first, this sounds like a recipe for disaster. But it offers some pretty interesting benefits:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Media queries are standardized right out the gate.&lt;/strong&gt; Instead of loosely defining breakpoints like &lt;code&gt;max-width: 860px&lt;/code&gt;, you can simply append &quot;small / medium / large&quot; to the front of your HTML classes. For example, here&apos;s how you could dynamically change an element&apos;s size between mobile and tablet: &lt;code&gt;class=&quot;h-16 w-16 md:w-24 md:h-24&quot;&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Shorthands can speed up experienced teams,&lt;/strong&gt; without abstracting the CSS away too much. Since every class is a 1-1 with a CSS property (or small set of properties), you are still translating to core CSS concepts every time. This dispells the fear of using all-in-one frameworks like Material UI, where the CSS is completely hidden from developers most of the time.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;This framework has a &lt;em&gt;laundry list&lt;/em&gt; of pros and cons that deserves its own article. Luckily, @swyx wrote an &lt;a href=&quot;https://dev.to/swyx/why-tailwind-css-2o8f&quot;&gt;amazing evaluation&lt;/a&gt; for his own needs that&apos;s worth reading 😁&lt;/p&gt;&lt;p&gt;I also want to mention &lt;a href=&quot;https://bulma.io/&quot;&gt;Bulma&lt;/a&gt; in this section, in case the breadth of Tailwind is too intimidating for you. I&apos;d consider it a &quot;Tailwind-lite&quot; with a lot of the same philosophies.&lt;/p&gt;&lt;h3&gt;Bare-bones UI libraries - Reach UI&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://reach.tech/&quot;&gt;Reach UI&lt;/a&gt; is my favorite example in the &quot;functionality-first&quot; camp. In short, Reach UI gives you a set of React components with prebuilt aria labels, state management, and some lightweight CSS styles sprinkled on top (mostly for changing &lt;code&gt;display&lt;/code&gt; properties). The goal here isn&apos;t to enforce a visual identity; it&apos;s to provide the &lt;em&gt;least amount of code possible&lt;/em&gt; to create usable, accessible UI elements ✨&lt;/p&gt;&lt;p&gt;In the end, you&apos;re free to add your own layer of styling and JS logic on top. The documentation actually guides you through overriding the defaults! I&apos;m a CSS warrior myself, so I lean on this type of CSS framework to get things done (as long as I&apos;m using React of course).&lt;/p&gt;&lt;p&gt;I&apos;d consider this sort of framework to be &lt;strong&gt;a master-class on &lt;a href=&quot;https://kentcdodds.com/blog/inversion-of-control&quot;&gt;inversion of control&lt;/a&gt;,&lt;/strong&gt; and I hope more frameworks pick up on this pattern.&lt;/p&gt;&lt;h2&gt;Wrapping up&lt;/h2&gt;&lt;p&gt;Overall, I think writing off CSS frameworks is a bit of a hand-wavy statement to make given the huge landscape of tools out there right now. There will always be a place for prebuilt solutions like Material UI; if anything, I see them as one step removed from powerhouses like SquareSpace or Wordpress (closer to the code, but with a lot of details abstracted away). And for teams that don&apos;t want to be tied down, solutions like Tailwind and Reach UI should fit the bill for adding handrails without losing visual identity.&lt;/p&gt;&lt;p&gt;Still, keep hacking away Uriel! We all have opinions, so just use what works best for you and your team 😁&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Building a sexy, mobile-ready navbar in any web framework</title><link>https://bholmes.dev/blog/building-a-sexy-mobile-ready-navbar-in-any-web-framework/</link><guid isPermaLink="true">https://bholmes.dev/blog/building-a-sexy-mobile-ready-navbar-in-any-web-framework/</guid><description>Let&apos;s build a slick navbar you can use in React, Svelte, or vanilla ES6 JS. We&apos;ll create an animated hamburger dropdown as well!</description><pubDate>Tue, 06 Oct 2020 22:44:09 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;I&apos;ve been building a lot more static sites recently, and every one of them needs the same thing:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;A nice and &lt;em&gt;responsive&lt;/em&gt; navigation bar&lt;/strong&gt; with logo on the left, links on the right 💪&lt;/li&gt;&lt;li&gt;For mobile screens, collapse those links on the right into a &lt;strong&gt;hamburger menu with a dropdown&lt;/strong&gt; 🍔&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Hit all the marks for accessibility&lt;/strong&gt;: semantic HTML, keyboard navigation, and more ♿️&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Add some polished animations&lt;/strong&gt; for that &lt;em&gt;sleek, modern feel&lt;/em&gt; ✨&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Oh, and implement it using whatever framework the team is using. This may sound daunting... but after bouncing between &lt;a href=&quot;https://reactjs.org/&quot;&gt;React&lt;/a&gt;, &lt;a href=&quot;https://svelte.dev/&quot;&gt;Svelte&lt;/a&gt;, and plain-ole JS, and I think I&apos;ve found a solid solution you can take with you wherever you go.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Onwards!&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;First, what&apos;s the end goal?&lt;/h2&gt;&lt;p&gt;Here&apos;s a screen-grab from my most recent project: redesigning the &lt;a href=&quot;https://hack4impact.org/&quot;&gt;Hack4Impact&lt;/a&gt; nonprofit site.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/i/9zokfxj7g5lsp6248d8m.gif&quot; alt=&quot;Demoing responsive mobile nav-bar with screen resize&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Ignore the cats. We needed some purrfect placeholders while we waited on content&lt;/em&gt; 😼&lt;/p&gt;&lt;p&gt;This has some fancy bells and whistles like that background blur effect, but it covers the general &quot;formula&quot; we&apos;re after!&lt;/p&gt;&lt;p&gt;##Lay down some HTML&lt;/p&gt;&lt;p&gt;Let&apos;s define the general structure of our navbar first.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;nav&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;logo&quot;&lt;/span&gt;&lt;span&gt; href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;dope-logo.svg&quot;&lt;/span&gt;&lt;span&gt; alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Our professional logo (ideally an svg!)&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;mobile-dropdown-toggle&quot;&lt;/span&gt;&lt;span&gt; aria-hidden&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;!-- Cool hamburger icon --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;dropdown-link-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/about&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;About Us&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/work&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;Our Work&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;nav&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;A few things to note here:&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;We&apos;re &lt;em&gt;not&lt;/em&gt; using an unordered list (ul) for our links here. You might see this recommendations floating around the web, and it&apos;s certainly a valid one! However, &lt;a href=&quot;https://css-tricks.com/wrapup-of-navigation-in-lists/&quot;&gt;this nuanced for / against piece from Chris Coyier&lt;/a&gt; really solidified things for me. In short: lists aren&apos;t &lt;em&gt;required&lt;/em&gt; for a11y concerns (the problem is minimal at best), so we can ditch them if we have a fair reason to do so. In our case, we actually &lt;em&gt;need&lt;/em&gt; to ditch the list so we can add our &lt;code&gt;dropdown-link-container&lt;/code&gt; without writing invalid HTML. To understand what I mean, &lt;a href=&quot;https://dev.to/bholmesdev/comment/16hi9&quot;&gt;I clarified the issue to a kind commenter here!&lt;/a&gt;&lt;/li&gt;&lt;li&gt;You&apos;ll notice our &lt;code&gt;dropdown-link-container&lt;/code&gt; element, which wraps around all our links &lt;em&gt;except&lt;/em&gt; the logo. This &lt;code&gt;div&lt;/code&gt; won&apos;t do anything fancy for desktop users. But once we hit our mobile breakpoint, we&apos;ll hide these elements in a big dropdown triggered by our &lt;code&gt;mobile-dropdown-toggle&lt;/code&gt; button.&lt;/li&gt;&lt;li&gt;We&apos;re slapping an &lt;code&gt;aria-hidden&lt;/code&gt; attribute on our dropdown toggle. For a simple nav like this, there&apos;s no reason for a screenreader to pick up on this button; it can always pick up on all our links even when they&apos;re &quot;visually hidden&quot;, so there&apos;s no toggling going on 🤷‍♀️ Still, if you really want to mimic the &quot;toggle&quot; effect for these users (which you should for super busy navbars), you can &lt;a href=&quot;https://www.w3.org/WAI/GL/wiki/Using_aria-expanded_to_indicate_the_state_of_a_collapsible_element&quot;&gt;look into adding &lt;code&gt;aria-expanded&lt;/code&gt; to your markup&lt;/a&gt;. This is getting a bit in the weeds for this article though, so you can use my easy-out for now.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;For those following along at home, you should have something like this:&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/zYqQvxj&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;h2&gt;Now, some CSS&lt;/h2&gt;&lt;p&gt;Before worrying about all that mobile functionality, let&apos;s spiff up the &lt;em&gt;widescreen&lt;/em&gt; experience.&lt;/p&gt;&lt;h3&gt;Our base styles&lt;/h3&gt;&lt;p&gt;To start, we&apos;ll set up the alignment and width for our navbar.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;nav&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  max-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1200&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;/* should match the width of your website content */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;flex&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  align-items&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;center&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;/* center each of our links vertically */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  margin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;/* center all our content horizontally when we exceed that max-width */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.logo&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  margin-right&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;/* push all our links to the right side, leaving the logo on the left */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.dropdown-link-container&lt;/span&gt;&lt;span&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  margin-left&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;/* space out all our links */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.mobile-dropdown-toggle&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;/* hide our hamburger button until we&apos;re on mobile */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;max-width&lt;/code&gt; property is an important piece here. Without it, our nav links will get pushed &lt;em&gt;wayyyy&lt;/em&gt; to the right (and our logo &lt;em&gt;wayyyy&lt;/em&gt; to the left) for larger screens. Here&apos;s a little before-and-after to show you what I mean.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/i/lau0lemuw8j7qoih7qvr.gif&quot; alt=&quot;Nav bar with some bad resizing&quot; /&gt;&lt;/p&gt;&lt;p&gt;**Before: ** Our nav elements stick to the edges of the screen. This doesn&apos;t match up with our page content very well, and makes navigation awkward on larger devices.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/i/q3fh0jg2lnz4eph7tzf4.gif&quot; alt=&quot;Nav bar with some good resizing&quot; /&gt;&lt;/p&gt;&lt;p&gt;**After: ** Everything is &lt;em&gt;beautifully&lt;/em&gt; aligned, making our website a lot more &quot;scan-able.&quot;&lt;/p&gt;&lt;p&gt;Of course, you can add padding, margins, and background colors to-taste 👨‍🍳 But as long as you have a &lt;code&gt;max-width&lt;/code&gt; and &lt;code&gt;margin: auto&lt;/code&gt; for centering the nav on the page, you&apos;re 90% done already! Here&apos;s another pen to see it in action:&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/eYZapdM&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;h3&gt;Adding the dropdown&lt;/h3&gt;&lt;p&gt;Alright, now let&apos;s tackle our dropdown experience. First, we&apos;ll just focus on re-styling our links into a vertical column that takes up the height of the page:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;max-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;768&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;/* arbitrary breakpoint, around the size of a tablet */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .dropdown-link-container&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /* first, make our dropdown cover the screen */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    position&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;fixed&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    top&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    left&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    right&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;vh&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /* fix nav height on mobile safari, where 100vh is a little off */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;-webkit-fill-available&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /* then, arrange our links top to bottom */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;flex&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    flex-direction&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;column&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /* center links vertically, push to the right horizontally.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;       this means our links will line up with the rightward hamburger button */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    justify-content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;center&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    align-items&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;flex-end&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /* add margins and padding to taste */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    margin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    padding-left&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;vw&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    padding-right&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;vw&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    background&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;lightblue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is pretty standard for the most part. Just a few things of note here:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;First, we use &lt;code&gt;position: fixed&lt;/code&gt; to align our dropdown to the top of our &lt;em&gt;viewport&lt;/em&gt;.&lt;/strong&gt; This is distinct from &lt;code&gt;position: absolute&lt;/code&gt;, which would shift the nav&apos;s position depending on our scroll position 😬&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Then, we use the &lt;code&gt;-webkit-fill-available&lt;/code&gt; property to fix our nav height in mobile Safari.&lt;/strong&gt; I&apos;m sure you&apos;re thinking &lt;em&gt;&quot;what, how is 100vh not 100% of the user&apos;s screen size? What did Apple do this time?&quot;&lt;/em&gt; Well, the problem comes from the iOS&apos; disappearing URL bar. When you scroll, a bunch of UI elements slide out of the way to give you more screen real estate. That&apos;s great and all, but it means anything that &lt;em&gt;used&lt;/em&gt; to take up 100% of the screen now needs to be resized! We have this problem on our &lt;a href=&quot;https://bitsofgood.org/&quot;&gt;Bits of Good nonprofit homepage&lt;/a&gt;:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/i/8v5kv4luayysgt93wrv1.gif&quot; alt=&quot;Safari nav height problem&quot; /&gt;&lt;/p&gt;&lt;p&gt;Notice the links aren&apos;t &lt;em&gt;quite&lt;/em&gt; vertically centered until we swipe away all the Safari buttons. If you have a bunch of links, this could lead to cut-off text and images too!&lt;/p&gt;&lt;p&gt;In the end, all you need is the override &lt;code&gt;height: -webkit-fill-available&lt;/code&gt; to specifically target this issue. Yes, feature flags like &lt;code&gt;-webkit&lt;/code&gt; are usually frowned upon. But since this issue only pops up in mobile Safari (a webkit browser), there really isn&apos;t a problem with this approach in my opinion 🤷‍♀️ Worst case, the browser falls back to &lt;code&gt;100vh&lt;/code&gt;, which is still a totally usable experience.&lt;/p&gt;&lt;p&gt;Finally, let&apos;s make sure our logo and dropdown buttons actually appear &lt;em&gt;on top of&lt;/em&gt; our dropdown. Because of &lt;code&gt;position:fixed&lt;/code&gt;, the dropdown will naturally hide everything underneath it until we add some &lt;code&gt;z-index&lt;/code&gt;ing:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;max-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;768&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .logo&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;.mobile-dropdown-toggle&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    z-index&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .mobile-dropdown-toggle&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;initial&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;/* override that display: none attribute from before */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .dropdown-link-container&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    z-index&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;/* we&apos;re gonna avoid using -1 here, since it could position our navbar below other content on the page as well! */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Squoosh this CodePen to our breakpoint size to see these styles at work:&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/KKzLdbR&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;h2&gt;Let&apos;s animate that dropdown&lt;/h2&gt;&lt;p&gt;Alright, we have most of our markup and styles finished up. Now, let&apos;s make that hamburger button do something!&lt;/p&gt;&lt;p&gt;We&apos;ll start with handling the menu button clicks. To show you how simple this setup is, I&apos;ll just use vanilla JS:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// get a ref to our navbar (assuming it has this id)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; navElement&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;main-nav&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;document.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;click&quot;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (event.target.classList.&lt;/span&gt;&lt;span&gt;contains&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;mobile-dropdown-toggle&quot;&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // when we click our button, toggle a CSS class!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    navElement.classList.&lt;/span&gt;&lt;span&gt;toggle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;dropdown-opened&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we&apos;ll animate our dropdown into view whenever that &lt;code&gt;dropdown-opened&lt;/code&gt; class gets applied:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* inside the same media query from before */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;max-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;768&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .dropdown-link-container&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /* our initial state */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;/* fade out */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    transform&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;translateY&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-100&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;/* move out of view */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    transition&lt;/span&gt;&lt;span&gt;: transform &lt;/span&gt;&lt;span&gt;0.2&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;, opacity &lt;/span&gt;&lt;span&gt;0.2&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;/* transition these smoothly */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  nav&lt;/span&gt;&lt;span&gt;.dropdown-opened&lt;/span&gt;&lt;span&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; .dropdown-link-container&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    opacity&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;/* fade in */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    transform&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;translateY&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;/* move into view */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nice! With just a few lines of CSS, we just defined a little fade + slide in effect whenever we click our dropdown. You can mess around with it here. Modify the transitions however you wish!&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/jOqoQRZ&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;h3&gt;Adapting for &lt;em&gt;big boy&lt;/em&gt; components&lt;/h3&gt;&lt;p&gt;Alright, I know some of you want to slide this in your framework of choice at this point. Well, it shouldn&apos;t be too difficult! You can &lt;strong&gt;keep all the CSS the same,&lt;/strong&gt; but here&apos;s a component snippet you can plop into React:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; BigBoyNav&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;mobileNavOpened&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setMobileNavOpened&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; toggleMobileNav&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; setMobileNavOpened&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;mobileNavOpened);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;nav&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{mobileNavOpened &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &apos;dropdown-opened&apos;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &apos;&apos;&lt;/span&gt;&lt;span&gt;}&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;mobile-dropdown-toggle&quot;&lt;/span&gt;&lt;span&gt; onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{toggleMobileNav} &lt;/span&gt;&lt;span&gt;aria-hidden&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;nav&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And one for Svelte:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;!-- ...might&apos;ve included this to show how simple Svelte is :) --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	let&lt;/span&gt;&lt;span&gt; mobileNavOpened &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; toggleMobileNav&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; mobileNavOpened &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;mobileNavOpened;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;nav&lt;/span&gt;&lt;span&gt; className:mobileNavOpened&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;dropdown-opened&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;mobile-dropdown-toggle&quot;&lt;/span&gt;&lt;span&gt; on:click&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{toggleMobileNav}&lt;/span&gt;&lt;span&gt; aria-hidden&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;nav&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...You get the point. It&apos;s a toggle :laughing:&lt;/p&gt;&lt;h2&gt;The little things&lt;/h2&gt;&lt;p&gt;We have a pretty neat MVP at this point! I just left a couple accessiblity pieces for the end to get you to the finish line 🏁&lt;/p&gt;&lt;h3&gt;Collapse that dropdown when you click a link&lt;/h3&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; You can skip this if you&apos;re using a vanilla solution like Jekyll, Hugo or some plain HTML. In those cases, the entire page will reload when you click a link, so there&apos;s no need to hide the dropdown!&lt;/em&gt;&lt;/p&gt;&lt;p&gt;If we&apos;re gonna cover the users entire screen, we should probably hide that dropdown again once they choose the link they want. We could just &lt;em&gt;any&lt;/em&gt; click events in our dropdown like so:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;document.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;click&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // if we clicked on something inside our dropdown...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (ourDropdownElement.&lt;/span&gt;&lt;span&gt;contains&lt;/span&gt;&lt;span&gt;(event.target)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    navElement.classList.&lt;/span&gt;&lt;span&gt;remove&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;dropdown-opened&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...but this wouldn&apos;t be super accessible :sweat:. Sure, it handles mouse clicks, but how will it fare against keyboard navigation with the &quot;tab&quot; key? In that case, the user will tab to the link they want, hit &quot;enter,&quot; and stay stuck in &lt;code&gt;dropdown-opened&lt;/code&gt; without any feedback!&lt;/p&gt;&lt;p&gt;Luckily, there&apos;s a more &quot;declarative&quot; way to get around this problem. Instead of listening for user clicks, we can just listen for whenever the route changes! This way, we don&apos;t need to consider how the user navigates through our dropdown links; Just listen for the result.&lt;/p&gt;&lt;p&gt;Of course, this solution varies depending on your router of choice. Let&apos;s see how &lt;a href=&quot;http://nextjs.org/&quot;&gt;NextJS&lt;/a&gt; handles this problem:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; BigBoyNav&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; router&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useRouter&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;// grab the current route with a React hook&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; activeRoute&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; router.pathname;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // whenever &quot;activeRoute&quot; changes, hide our dropdown&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setMobileNavOpened&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, [activeRoute]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Vanilla &lt;a href=&quot;https://reactrouter.com/&quot;&gt;React Router&lt;/a&gt; should handle this problem the same way. Regardless of your framework, just make sure you trigger your state change whenever the active route changes :thumbsup:&lt;/p&gt;&lt;h3&gt;Handle the &quot;escape&quot; key&lt;/h3&gt;&lt;p&gt;For &lt;em&gt;even better&lt;/em&gt; keyboard accessiblity, we should also toggle the dropdown whenever the &quot;escape&quot; key is pressed. This is bound to a very specific user interaction, so we&apos;re free to add an event listener for this one:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// vanilla JS&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; escapeKeyListener&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; KeyboardEvent&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	event.key &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;Escape&apos;&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; navElement.classList.&lt;/span&gt;&lt;span&gt;remove&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;dropdown-opened&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;document.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;keypress&apos;&lt;/span&gt;&lt;span&gt;, escapeKeyListener);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...and for component frameworks, make sure you remove that event listener whenever the component is destroyed:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// React&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; escapeKeyListener&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; KeyboardEvent&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  event.key &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;Escape&apos;&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; setMobileNavOpened&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // add the listener &quot;on mount&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  document.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;keypress&apos;&lt;/span&gt;&lt;span&gt;, escapeKeyListener);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // remove the listener &quot;on destroy&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;removeEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;keypress&apos;&lt;/span&gt;&lt;span&gt;, escapeKeyListener);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, []);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;See a fully-functional React example 🚀&lt;/h2&gt;&lt;p&gt;If you&apos;re curious how this could all fit together in a React app, our entire &lt;a href=&quot;https://hack4impact.org/&quot;&gt;Hack4Impact&lt;/a&gt; website is accessible on CodeSandbox!&lt;/p&gt;&lt;p&gt;&lt;strong&gt;To check out the Nav component, &lt;a href=&quot;https://codesandbox.io/s/hack4impact-website-0kn5m?file=/components/shared/Nav/index.tsx&quot;&gt;head over here&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;And thanks to &lt;a href=&quot;https://thejameswang.me/&quot;&gt;James Wang&lt;/a&gt; for nudging me to write this article. Might have sparked a whole mini-series on how we built our splash page&lt;/em&gt;&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Writing a state machine in one line with TypeScript</title><link>https://bholmes.dev/blog/writing-a-state-machine-in-one-line-with-typescript/</link><guid isPermaLink="true">https://bholmes.dev/blog/writing-a-state-machine-in-one-line-with-typescript/</guid><description>State machines aren&apos;t just a nice library (hi XState); they&apos;re a way of thinking about your state. Let&apos;s jump into an example using TypeScript.</description><pubDate>Wed, 02 Sep 2020 15:35:45 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;Ah yes, state machines. That thing &lt;a href=&quot;https://twitter.com/DavidKPiano&quot;&gt;David K Piano&lt;/a&gt; keeps tweeting about, or that CS concept that pops up in college once a semester (and seemingly never returns...). As the frontend world gets more and more disatisfied with Redux, state machines are &lt;a href=&quot;https://mobx.js.org/README.html&quot;&gt;one&lt;/a&gt; &lt;a href=&quot;https://rxjs-dev.firebaseapp.com/&quot;&gt;of&lt;/a&gt; &lt;a href=&quot;https://github.com/facebookexperimental/Recoil&quot;&gt;&lt;em&gt;many&lt;/em&gt;&lt;/a&gt; &lt;a href=&quot;https://www.apollographql.com/docs/react/&quot;&gt;concepts&lt;/a&gt; devs are talking about these days.&lt;/p&gt;&lt;p&gt;But unlike Redux et al., &lt;strong&gt;state machines don&apos;t have to be a library you install into your project!&lt;/strong&gt; Once you understand them conceptually, they become their own way of thinking about problems.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;In short, this article should help you...&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Identify when boolean flags and state variables are getting too complex&lt;/li&gt;&lt;li&gt;Write your own scrappy state machine with no libraries&lt;/li&gt;&lt;li&gt;Learn a bit more about state machines as a concept, and when XState might be a good idea&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Onwards!&lt;/p&gt;&lt;p&gt;⚠️ &lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; we&apos;ll be using React for the following examples. Still, the core learning concepts transfer to any frontend framework&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;First, a scenario&lt;/h2&gt;&lt;p&gt;If we&apos;re talking UI complexity, form management is the easiest place to look. Let&apos;s say we have a simple sign-up screen we need to implement with a username and password. To make things a little interesting, let&apos;s say we&apos;re reviving the incredible childhood memory that is &lt;a href=&quot;https://cprewritten.net/&quot;&gt;Club Penguin&lt;/a&gt;!&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/i/kh3ub038zv8qvjy063fu.jpg&quot; alt=&quot;A hideously-perfect club penguin signup screen&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Try not the cringe. At least it&apos;s not built on Flash&lt;/em&gt; 😬&lt;/p&gt;&lt;p&gt;We also want to consider some scenarios as the user fills out the form. Namely, we should support &lt;strong&gt;a) password validation before you submit&lt;/strong&gt; and &lt;strong&gt;b) disabling the submit button while we&apos;re sending to the API.&lt;/strong&gt; Here&apos;s what that flow might look like:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/i/trzz468dhfgnv9lq798w.gif&quot; alt=&quot;A gif explaining the signup process, with &amp;quot;password invalid&amp;quot; popup and bottom submission states&quot; /&gt;&lt;/p&gt;&lt;h2&gt;A common approach: brute-force booleans&lt;/h2&gt;&lt;p&gt;First, let&apos;s cover the approach a lot of devs might take &lt;a href=&quot;https://hackernoon.com/handling-loading-actions-the-proper-way-in-redux-t3k36e8&quot;&gt;(especially coming from a Redux background)&lt;/a&gt;. Based on the interactions we want, we should probably have some flags for&lt;/p&gt;&lt;ol&gt;&lt;li&gt;When the password is invalid&lt;/li&gt;&lt;li&gt;When we&apos;re submitting to the API&lt;/li&gt;&lt;li&gt;Whether we&apos;ve submitted successfully (maybe to move to the next screen)&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I won&apos;t bore you with the HTML + &lt;em&gt;colorful&lt;/em&gt; CSS we need (check this &lt;a href=&quot;https://codesandbox.io/s/forms-the-bad-way-mvm2w&quot;&gt;CodeSandbox&lt;/a&gt; for such goodies!), so let&apos;s just look at the pieces we care about:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; ClubPenguinSignup&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;invalid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setInvalid&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;submitting&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setSubmitting&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;submitted&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setSubmitted&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // state vars for username and password, markup, etc.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For the submitting / submitted flags, we can use a nice callback function for whenever our form submits:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; onSubmit&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;FormEvent&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  event.&lt;/span&gt;&lt;span&gt;preventDefault&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setSubmitting&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// we&apos;re now submitting&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; addedUser&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; arcticAuthService&lt;/span&gt;&lt;span&gt;({ username, password });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (addedUser?.success) { &lt;/span&gt;&lt;span&gt;// if signup call worked...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setSubmitting&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// we&apos;re no longer submitting&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  	setSubmitted&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// we&apos;ve submitted&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, we can make a super basic callback to validate our password as the user types it in. In this case, we&apos;ll listen for whenever the input&apos;s value changes (i.e. using a &lt;a href=&quot;https://goshakkk.name/controlled-vs-uncontrolled-inputs-react/&quot;&gt;controlled input&lt;/a&gt;), and run the value through an insecure phrase checker:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; onChangePassword&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;FormEvent&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;HTMLInputElement&lt;/span&gt;&lt;span&gt;&amp;gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setPassword&lt;/span&gt;&lt;span&gt;(event.currentTarget.value);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  checkPasswordSecurity&lt;/span&gt;&lt;span&gt;(event.currentTarget.value);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; checkPasswordSecurity&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;changedPassword&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; insecure &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// we first assume the value is secure (excuse the double negative)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  [&lt;/span&gt;&lt;span&gt;&quot;club&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;penguin&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;puffle&quot;&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;badWord&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (changedPassword.&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(badWord)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      insecure &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setInvalid&lt;/span&gt;&lt;span&gt;(insecure);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Where it starts to get hairy&lt;/h3&gt;&lt;p&gt;Great! This doesn&apos;t seem too bad... but we&apos;re not done yet. If you check that mockup again, you&apos;ll notice that our button has 3 different indicators to display (normal, loading, and finished). Since we&apos;re using separate boolean flags for each of these, we&apos;ll need some mappers to set the button backgrounds + flavor text:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; getButtonLabel&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (submitting) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; &quot;•••&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (submitted) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; &quot;Time to play!&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; &quot;Let&apos;s get sliding!&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; getButtonClass&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (submitting) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; &quot;submitting&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (submitted) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; &quot;submitted&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (invalid) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; &quot;invalid&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;submit&quot;&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;getButtonClass&lt;/span&gt;&lt;span&gt;()}&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;span&gt;getButtonLabel&lt;/span&gt;&lt;span&gt;()}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we only need mappers for a single element, this doesn&apos;t seem &lt;em&gt;that&lt;/em&gt; terrible. Still, this could easily start ballooning out of control as we add more UI and more state variables...&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;usenameTaken&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setUsernameTaken&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;apiError&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setApiError&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;serverAtMaxCapacity&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setServerAtMaxCapacity&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;invalid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setInvalid&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;submitting&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setSubmitting&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;submitted&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setSubmitted&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; getButtonClass&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // 100 lines of ifs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&apos;re also allowing for a lot of states that shouldn&apos;t be possible. For example, we should never be &quot;submitting&quot; and &quot;submitted&quot; at the same time, and neither of these should be &lt;code&gt;true&lt;/code&gt; when the password is invalid. Considering the crazy state explosion above, we&apos;ll end up restling all these variables to prevent such invalid states.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// api responds that servers are at max capacity, so no sign ups allowed&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;setServerAtMaxCapacity&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;setSubmitting&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;setSubmitted&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;setApiError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If anything, we just want to have a boolean with more than 2 values so we&apos;re not switching flags all over the place. Luckily, &lt;em&gt;TypeScript gives us such superpowers&lt;/em&gt; 💪&lt;/p&gt;&lt;h2&gt;Our new approach: the poor man&apos;s state machine&lt;/h2&gt;&lt;p&gt;As you may have guessed, we can solve this boolean bonanza with a simple state machine. I&apos;ve heard this approach called the &quot;&lt;a href=&quot;https://twitter.com/housecor/status/1292111064314851329&quot;&gt;poor man&apos;s state machine&lt;/a&gt;&quot; which is a super apt title as well!&lt;/p&gt;&lt;p&gt;All we need is &lt;s&gt;the XState library&lt;/s&gt; a one-liner to model our state variables as a single type:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; FormState&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;idle&apos;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &apos;invalid&apos;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &apos;submitting&apos;&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;span&gt; &apos;submitted&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You could certainly use an enum for this as well. I just prefer string literals since they&apos;re a bit shorter + more readable (I also wrote a &lt;a href=&quot;https://dev.to/bholmesdev/using-enums-with-string-values-in-typescript-consider-string-literals-instead-486e&quot;&gt;short article on the subject&lt;/a&gt; if you&apos;re still an enum stan).&lt;/p&gt;&lt;p&gt;With our type defined, we can condense all our state variables into one:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;formState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setFormState&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;FormState&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;&quot;idle&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Refactoring our password and submit callback is pretty easy from here.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; checkIfPasswordIsSecure&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;changedPassword&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setFormState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;idle&quot;&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// not invalid yet&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    [&lt;/span&gt;&lt;span&gt;&quot;club&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;penguin&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;puffle&quot;&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;badWord&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (changedPassword.&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(badWord)) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setFormState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;invalid&quot;&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// oops! Looks like it&apos;s invalid after all&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; onSubmit&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;FormEvent&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  event.&lt;/span&gt;&lt;span&gt;preventDefault&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (formState &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &quot;invalid&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// don&apos;t submit if our password is invalid&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  setFormState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;submitting&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; addedUser&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; arcticAuthService&lt;/span&gt;&lt;span&gt;({ username, password });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (addedUser?.id) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  	setFormState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;submitted&quot;&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// no need to set submitting to false, since we don&apos;t have 2 states to consider anymore!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And remember those button &lt;code&gt;className&lt;/code&gt;s we needed to map? Well, since our state is represented as a string, we can just pass these directly to our CSS ✨&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;submit&quot;&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{formState &lt;/span&gt;&lt;span&gt;/* our state is our CSS */&lt;/span&gt;&lt;span&gt;}&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This approach is super handy for keeping our CSS in check; instead of constantly adding and removing classes, we can just switch out which class gets applied.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://codesandbox.io/s/club-penguin-with-state-machines-1klrq?file=/src/styles.css&quot;&gt;Here&apos;s a working CodeSandbox&lt;/a&gt; using our new approach ✨&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;Going further 🚀&lt;/h2&gt;&lt;p&gt;Of course, this is a pretty simple example that may not &lt;em&gt;quite&lt;/em&gt; fit your use case. For example, you may want to be in multiple states at a given time, or guard against &quot;invalid transitions&quot; (for example, it shouldn&apos;t be possible to go from &lt;code&gt;idle&lt;/code&gt; to &lt;code&gt;submitted&lt;/code&gt; without passing through &lt;code&gt;submitting&lt;/code&gt; first).&lt;/p&gt;&lt;p&gt;The former could just require multiple state variables, so consider creating multiple &lt;code&gt;FormState&lt;/code&gt; types to see how it feels. Still, you may have enough complexity that a state management library makes a lot of sense. Check out &lt;a href=&quot;https://xstate.js.org/docs/&quot;&gt;XState&lt;/a&gt; if this sounds like you!&lt;/p&gt;&lt;p&gt;To get your feet wet, I found a couple high quality demos around the Internet worth checking out:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=czi24DqUfSA&quot;&gt;This one&lt;/a&gt; on building a more complex ReactJS form.&lt;/strong&gt; It&apos;s long, but worth your time!&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=osaszd2aU9U&quot;&gt;This one&lt;/a&gt; on creating a Vanilla JS drag-and-drop interaction.&lt;/strong&gt; This is more CSS-intensive and speaks to the &lt;code&gt;className&lt;/code&gt; trick I showed above.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=hiT4Q1ntvzg&quot;&gt;This one&lt;/a&gt; on modeling UI with state machines across frameworks.&lt;/strong&gt; Best conference talk on the subject hands down.&lt;/li&gt;&lt;/ul&gt;&lt;/article&gt;</content:encoded></item><item><title>Before building your next static site with React, consider this</title><link>https://bholmes.dev/blog/before-building-your-next-static-site-with-react-consider-this/</link><guid isPermaLink="true">https://bholmes.dev/blog/before-building-your-next-static-site-with-react-consider-this/</guid><description>Here&apos;s when templating libraries like Pug might be better than a bunch of components</description><pubDate>Tue, 07 Jul 2020 13:48:25 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;⚠️ &lt;em&gt;&lt;strong&gt;Warning:&lt;/strong&gt; Potentially controversial opinions ahead! Turn back now if Gatsby is your lord and savior, and you serve to protect the React dogma.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;So I just finished building my &lt;a href=&quot;https://bholmes.dev/&quot;&gt;shiny new personal site&lt;/a&gt; ✨ I thought about using fancy, component-driven frameworks like &lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;Gatsby&lt;/a&gt; to stay on the &lt;em&gt;bleeding edge&lt;/em&gt; of web development, but after some reflection, I thought to myself...&lt;/p&gt;&lt;p&gt;&lt;em&gt;do I really need all this tooling to write some static HTML?&lt;/em&gt;&lt;/p&gt;&lt;p&gt;This ended up being a &lt;strong&gt;huge&lt;/strong&gt; learning experience on how to build a statically generated SPA from scratch (check out &lt;a href=&quot;https://github.com/bholmesdev/bholmesdev&quot;&gt;the README&lt;/a&gt; if you&apos;re curious how I approached it!). But it also taught me some valuable lessons on how far you can go while ditching the component libraries we all know and love.&lt;/p&gt;&lt;h2&gt;A little background: going from dynamic to static sites&lt;/h2&gt;&lt;p&gt;Okay, let&apos;s be real for a moment: component-level thinking &lt;em&gt;rules&lt;/em&gt; modern web development. Even as &lt;a href=&quot;https://svelte.dev/&quot;&gt;new frontend libraries&lt;/a&gt; and &lt;a href=&quot;https://redwoodjs.com/&quot;&gt;Ruby-esque frameworks&lt;/a&gt; hit the scene, they still rely on the same basic formula: write your markup and JS logic inside a bite-sized component, and compose those components with imports and exports. Whether those components are class-based, functional, or even at the DOM level (hello web components 👋), they&apos;re all focused on these ideas of &lt;strong&gt;logic isolation&lt;/strong&gt; and &lt;strong&gt;code reusability&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;Popular libraries like React and Vue have become such a one-size-fits-all solution that we don&apos;t even question them anymore. New to building literally anything on the Internet? Just run &lt;code&gt;npx create-react-app&lt;/code&gt; and get going!&lt;/p&gt;&lt;p&gt;...right?&lt;/p&gt;&lt;p&gt;I used to agree. But over the past couple years of Gatsby and JAMStack preaching, I&apos;ve also come to realize that &lt;em&gt;damn, we&apos;re making some &lt;strong&gt;fat&lt;/strong&gt; JS bundles&lt;/em&gt;. With all these JS-based components, we&apos;re shipping entire rendering libraries to the browser, even for a company&apos;s plane ole&apos; static splash page!&lt;/p&gt;&lt;p&gt;Before getting cynical, it&apos;s worth remembering why these libraries were made in the first place. React wasn&apos;t created because Facebook should be a better static site; it was created because Facebook is a &lt;strong&gt;super dynamic, ultra complex web &lt;em&gt;app&lt;/em&gt;&lt;/strong&gt; with logins, trackers, home feeds, settings menus, etc etc etc. That involves a &lt;em&gt;ton&lt;/em&gt; of data / state management, which means, well, a whole lot of JavaScript to construct the web page. For this use case, it makes perfect sense to build and use a UI rendering library that&apos;s &lt;strong&gt;state driven&lt;/strong&gt;, rather than &lt;strong&gt;markup driven.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;This is why Gatsby (a popular static site generator)  came years after, say Redux state management. Devs were mostly fascinated with building dynamic, JS-driven experiences that state, props, and data objects could solve. It wasn&apos;t until later that devs started wondering how they could bend these JS-heavy libaries to their will for static site creation.&lt;/p&gt;&lt;p&gt;If you ask me, it&apos;s pretty ironic that it takes a 500 MB directory called &lt;code&gt;node_modules&lt;/code&gt; to generate a website with... as little JavaScript as possible.&lt;/p&gt;&lt;p&gt;Still, I can&apos;t say I&apos;m surprised either. When you&apos;re taking a library like React, which &lt;em&gt;needs&lt;/em&gt; JavaScript to render anything to the page, you&apos;ll obviously need even more JavaScript to process all that rendering logic to begin with. Fire really does fight fire.&lt;/p&gt;&lt;h3&gt;So... why use React on a static site?&lt;/h3&gt;&lt;p&gt;At first, it kinda feels like using a chainsaw to slice a loaf of bread. Why use a rendering library like React when you have zero-to-little rerendering to worry about?&lt;/p&gt;&lt;p&gt;In short, &lt;strong&gt;hydration.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/i/rhsfzpxjmt12obcjyhtd.gif&quot; alt=&quot;Flash dance scene where water bucket falls onto dancer sitting in chair&quot; /&gt; &lt;em&gt;If you don&apos;t get this reference, &lt;a href=&quot;https://www.youtube.com/watch?v=bAXX73HEXXs&quot;&gt;go culture yourself&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;For those unfamiliar, hydration basically lets us write a dynamic, state-driven webpage, but also render as much of the page ahead-of-time as possible using static HTML. &lt;a href=&quot;https://www.gatsbyjs.org/docs/react-hydration/&quot;&gt;The Gatsby blog&lt;/a&gt; does a great job explaining this process, but here&apos;s a quick step-by-step:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Your app exists as a big bundle of components, much like a &lt;code&gt;create-react-app&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;The static site generator comes along and renders out this bundle at build time. Now, instead of shipping a blank HTML file to the user, you can send the entire page&apos;s markup for a speedy page load.&lt;/li&gt;&lt;li&gt;Now, we want to do some stateful component magic &lt;em&gt;alongside&lt;/em&gt; the static HTML we just built. To pull this off, we can look at the HTML page that&apos;s already been generated and compare it against our tree of components. When we find a component that &lt;em&gt;does&lt;/em&gt; some state management craziness, we&apos;ll slot it into our existing HTML &lt;em&gt;without rerendering the entire page.&lt;/em&gt; In other words, we &lt;strong&gt;hydrate&lt;/strong&gt; our markup with some stateful components.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Seems slick! This comes in handy when you have some JS you want to use (say, a &lt;a href=&quot;https://www.react-spring.io/&quot;&gt;crazy animation library&lt;/a&gt; for added spice) that only apply to small areas of your otherwise-static site. But as you might have guessed, we&apos;ll need to ship the entire component library to the client in order to compare against the HTML. So it&apos;s still a fat bundle... but at least the user sees something on the first page load 🤷‍♀️&lt;/p&gt;&lt;h3&gt;And what if you don&apos;t need state management?&lt;/h3&gt;&lt;p&gt;Now, React doesn&apos;t make as much sense. If we just need to handle some button clicks, we should probably just write a couple lines of vanilla JS instead of, you know, shipping the entire React library 😬&lt;/p&gt;&lt;p&gt;For some perspective, here&apos;s some common dev requests when building a static site:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;We want to break down our static site into reuseable UI components&lt;/strong&gt; that can accept some JS objects as parameters (aka &quot;props&quot;). This lets us, say, turn a list of blog post links into a bunch of clickable cards on our homepage.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;We need to fetch some information at build time&lt;/strong&gt; to slap into our production site. For example, we can go get some Twitter posts at build time to slide into our site&apos;s homepage, without shipping any API calls or exposing secret keys.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;We need to generate a bunch of URL routes&lt;/strong&gt; from either a directory of files or a fat JSON object of content. For instance, we have a folder of markdown files that we want to turn into a personal blog, making each file its own URL on the interwebs.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;These are all great reasons to use static site generators. But looking at this list, only the &lt;em&gt;first&lt;/em&gt; requirement actually involves a component library. And even then, we may not need to worry about rerenders or componentized state management; it&apos;s mostly done at build time! If only there was a way to make our markup reuseable and template-able, without shipping a bunch of unused JS...&lt;/p&gt;&lt;h2&gt;(Re)enter: Pug&lt;/h2&gt;&lt;p&gt;That&apos;s right, good ole&apos; Pug (formerly Jade). You know, that cute little templating library you used on your last CodePen, or maybe the weird looking HTML you found on an Express server template. It&apos;s a mighty little library from a pre-React era, before component-driven state management was even a thing.&lt;/p&gt;&lt;p&gt;It also uses a &lt;a href=&quot;https://pugjs.org/language/tags.html&quot;&gt;simplified syntax&lt;/a&gt; for HTML that makes it a bit easier to type / look at, which I&apos;m personally a fan of 😁&lt;/p&gt;&lt;p&gt;So why am I bringing up this jaded (pun intended) templating library? Well, let&apos;s run through some of Pug&apos;s defining features to see what it can do. I&apos;m hungry so we&apos;ll use a donut shop for examples 🍩:&lt;/p&gt;&lt;h3&gt;1. You can take in some JavaScript data and turn it into HTML elements&lt;/h3&gt;&lt;p&gt;This opens the door for all kinds of craziness, like looping, conditional &quot;if&quot; blocks, defining text content... you name it:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;.krispy-kreme-menu&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; menuItems.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; ===&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	    p&lt;/span&gt;&lt;span&gt;.alert&lt;/span&gt;&lt;span&gt; Sorry! Sold out of gooey deliciousness :(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	else&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;         dl&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;         each&lt;/span&gt;&lt;span&gt; item &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; menuItems&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;             dt&lt;/span&gt;&lt;span&gt; #{&lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;             dd&lt;/span&gt;&lt;span&gt; #{&lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;price&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And at the JavaScript level:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; stringOfRenderedHTML&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; pug.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/filename.pug&apos;&lt;/span&gt;&lt;span&gt;, { menuItems: [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;donuts] })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// spit out this string of HTML into a .html file at build time&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. You can compose multiple HTML files (now &lt;code&gt;.pug&lt;/code&gt; files) into a single page layout&lt;/h3&gt;&lt;p&gt;For example, you can create a navigation bar in one file...&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// nav.pug&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;nav&lt;/span&gt;&lt;span&gt;.krispy-kreme-navigation&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	a&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; Home&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	a&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/donuts&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; Buy some donuts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	a&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/contact&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; Somehow complain about your donuts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;... and import into another file:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// index.pug&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;html&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    body&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        include&lt;/span&gt;&lt;span&gt; nav.pug&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        main&lt;/span&gt;&lt;span&gt;.donut-content&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            ul&lt;/span&gt;&lt;span&gt;.list-of-tastiness&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	       .&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can go even deeper by passing parameters / &quot;props&quot; between these files. Check out this &lt;code&gt;mixin&lt;/code&gt; syntax:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// nav-mixins.pug&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;mixin &lt;/span&gt;&lt;span&gt;NavBar&lt;/span&gt;&lt;span&gt;(links)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	each&lt;/span&gt;&lt;span&gt; link &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; links&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		a&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;href&lt;/span&gt;&lt;span&gt;=link.href&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; link.text&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// index.pug&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;include&lt;/span&gt;&lt;span&gt; nav-mixins.pug&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;html&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	body&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		+&lt;/span&gt;&lt;span&gt;NavBar&lt;/span&gt;&lt;span&gt;(donutLinksPassedDownByJS)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		main&lt;/span&gt;&lt;span&gt;.donut-content&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			ul&lt;/span&gt;&lt;span&gt;.list-of-tastiness&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, we can consider each mixin an &lt;code&gt;export&lt;/code&gt; statement from &lt;code&gt;nav-mixins.pug&lt;/code&gt;. Then, when we &lt;code&gt;include&lt;/code&gt; this file somewhere else, those mixins become usable via the &lt;code&gt;+&lt;/code&gt; decorator (aka our &quot;import&quot; statement).&lt;/p&gt;&lt;h3&gt;So in summary...&lt;/h3&gt;&lt;p&gt;✅ We can turn JSON objects into &lt;em&gt;&lt;strong&gt;static&lt;/strong&gt;&lt;/em&gt; HTML with a one-line script (&lt;code&gt;pug.render(filename, someJSON)&lt;/code&gt;)&lt;/p&gt;&lt;p&gt;✅ We can break up our layout into multiple files using imports&lt;/p&gt;&lt;p&gt;✅ We can define component-like &quot;mixins&quot; for reusability and data passing&lt;/p&gt;&lt;p&gt;...in other words, &lt;strong&gt;we get to make our UIs with components, but without sending a bunch of libraries to the client!&lt;/strong&gt;&lt;/p&gt;&lt;h3&gt;But wait, this idea is nothing new!&lt;/h3&gt;&lt;p&gt;I know! Backend servers have been doing this for decades.&lt;/p&gt;&lt;p&gt;Let&apos;s consider the server-driven model you would use for, say, an &lt;a href=&quot;https://expressjs.com/&quot;&gt;ExpressJS&lt;/a&gt; app. Everytime you hit an API endpoint, the server may go look up some info from a database, roll that data into an HTML template (probably using Pug), and send it back to the user. Same goes for PHP, C#, GoLang, or whatever exotic server you&apos;ve seen before.&lt;/p&gt;&lt;p&gt;But guess what? A static site generator &lt;em&gt;does the exact same thing!&lt;/em&gt; The only difference is that now, instead of doing all the data fetching + templating in an &lt;em&gt;API endpoint&lt;/em&gt;, we&apos;re doing it in a &lt;em&gt;build script&lt;/em&gt; that we call when the website actually gets deployed. For those familiar, this is that fancy script you tell Netlify to run when you first deploy your site.&lt;/p&gt;&lt;p&gt;Servers used this templating model long before we were making crazy, ultra-dynamic web apps. So my question is this: when your website just has some static landing pages, a few lines of JS, and &lt;em&gt;maybe&lt;/em&gt; a blog to generate... why throw away this idea of templating for component libraries?&lt;/p&gt;&lt;h2&gt;Call to action 👉 check out 11ty&lt;/h2&gt;&lt;p&gt;I just found out about &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;&lt;strong&gt;11ty&lt;/strong&gt;&lt;/a&gt; (pronounced &quot;eleven-tee&quot;), a static site generator built with this &lt;em&gt;exact&lt;/em&gt; philosophy in mind. You can choose the HTML templating language of your choice (Markdown, Haml, Pug, Nunjucks, and a bunch more), and let the framework handle all the complicated routing and page generation for you. If you&apos;re trying to build a portfolio site with a blog, a promotional splash page for a company, or anything super static-y, this is honestly the best solution I can think of.&lt;/p&gt;&lt;p&gt;That said, I&apos;m &lt;em&gt;definitely&lt;/em&gt; not suggesting you give up your beautiful Gatsby site! There are some serious benefits to &lt;a href=&quot;https://www.gatsbyjs.org/docs/react-hydration/&quot;&gt;their hydration model&lt;/a&gt; in case you want any state management stuff. Plus, if your super comfortable in React-land and don&apos;t have time to pick up new tools, it&apos;s a pretty convenient choice with a huge community of support. Same goes for &lt;a href=&quot;https://gridsome.org/&quot;&gt;Gridsome&lt;/a&gt;, a Vue-based static site generator that works in a similar way.&lt;/p&gt;&lt;p&gt;Anyhow, whatever you end up using, I hope this article got you thinking a bit more about what static site generators &lt;em&gt;really&lt;/em&gt; do. With that, I&apos;ll leave you with this cute pug photo to stick in your mind 🐶&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/i/0rs79ok8vmppv9k66moi.jpg&quot; alt=&quot;Cute pug sitting on a bed&quot; /&gt;&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Use flexbox? Here&apos;s when CSS Grid is better</title><link>https://bholmes.dev/blog/flexbox-diehards-here-s-some-concrete-examples-of-when-css-grid-is-just-better/</link><guid isPermaLink="true">https://bholmes.dev/blog/flexbox-diehards-here-s-some-concrete-examples-of-when-css-grid-is-just-better/</guid><description>Everyone&apos;s talking about CSS Grid, but when is it really better than what you&apos;re already doing?</description><pubDate>Tue, 26 May 2020 16:34:01 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;&lt;em&gt;This blog post introduces several new terms you may be unfamiliar with: griddazzle, gridify, getting &quot;griddy&quot; with it, etc. I recommend using context clues for these definitions.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;I&apos;ll be honest, I was a flexbox diehard myself a while after CSS Grid took hold of page layouts. Even after taking Wes Bos&apos; CSS Grid course (&lt;a href=&quot;https://cssgrid.io/&quot;&gt;still the best resource hands-down if you&apos;re new to Grid!&lt;/a&gt;), I just didn&apos;t see the need to change my ways. Sure, flexbox sometimes needs an extra &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; or two to get layouts working... but hey, if it ain&apos;t broke, why fix it?&lt;/p&gt;&lt;p&gt;Still, I slowly forced myself to implement CSS Grid in some choice areas of my websites. Now, after a year of experimenting and getting increasingly &lt;em&gt;Griddy with it&lt;/em&gt;, I&apos;ve found some neat corners of CSS where flexbox struggles by comparison.&lt;/p&gt;&lt;p&gt;Posts comparing flexbox to Grid sometimes use simplified examples that don&apos;t get to the heart of flexbox&apos;s flaws. So, to convince all you flexers out there to join the dark side, here&apos;s some concrete examples of when CSS Grid really is &lt;strong&gt;alot&lt;/strong&gt; simpler in my own experience.&lt;/p&gt;&lt;h2&gt;1. When layouts have some reordering to do&lt;/h2&gt;&lt;p&gt;Let&apos;s consider a common scenario: you have a 2 column layout for desktop / laptop users, and you want it to wrap nicely to a single column for narrow screens.&lt;/p&gt;&lt;p&gt;Taking a quick glance at your team&apos;s mockups, you fly into your usual flex rhythm and end up here:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;outer-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;...column 1 content&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;...column 2 content&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .outer-container&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        /* create flexbox with a single row */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;flex&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        justify-content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;space-between&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    @media&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;max-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        .outer-container&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            /* wrap columns on top of each other for mobile */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            flex-direction&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;column&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          	/* alternatively, you can remove the flexbox entirely&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          	and let the columns wrap naturally */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          	display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;block&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Easy peasy! For a lot of use cases, this should work just fine.&lt;/p&gt;&lt;p&gt;But sometimes, your design team my have other ideas. Let&apos;s consider the layout below, with both a desktop and mobile mockup to implement:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/i/zvpfqnr6kmt9bib89r4c.png&quot; alt=&quot;contact-us-layout-change.png&quot; /&gt;&lt;/p&gt;&lt;p&gt;Well, that&apos;s a lot less fun. Not only should we reverse the ordering of our header and image, but we also need to move the final element of the first column (the &quot;find us in person&quot; address) to the end of our &lt;em&gt;second column&lt;/em&gt;. This is definitely a nail in our flexbox coffin 😕&lt;/p&gt;&lt;h3&gt;Time to gridazzle&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Fear not!&lt;/strong&gt; CSS grid has an elegant solution to the problem: [&lt;strong&gt;grid template areas&lt;/strong&gt;](&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Grid_Template_Areas&quot;&gt;Grid template areas - CSS: Cascading Style Sheets | MDN&lt;/a&gt;) ✨&lt;/p&gt;&lt;p&gt;With this tool, we can assign each HTML element a unique &quot;area&quot; in our layout. then, we let our grid container decide where to slap each area into our final layout. It&apos;s a lot like paint-by-numbers, but &lt;em&gt;gridified.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Here&apos;s a sample of how we could implement the above layout:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;!-- First, put all our elements on the page. --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- Notice we don&apos;t need wrapper &amp;lt;div&amp;gt;s for each column anymore! --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;outer-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;Contact Us&amp;lt;/&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;tagline-text&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;Want to learn more...&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/static/contact-us.jpg&quot;&lt;/span&gt;&lt;span&gt; alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;People flying around with laptops&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;address-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;...&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;form&lt;/span&gt;&lt;span&gt;&amp;gt;...&amp;lt;/&lt;/span&gt;&lt;span&gt;form&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .outer-container&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;grid&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /* use template-areas to define the structure of our 2 column page */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /*										  col 1 | col 2 														 */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    grid-template-areas&lt;/span&gt;&lt;span&gt;:  &lt;/span&gt;&lt;span&gt;&quot;header   header&quot;&lt;/span&gt;&lt;span&gt; /* row 1 (just the header h1) */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                          &quot;tagline  form&quot;&lt;/span&gt;&lt;span&gt; /* row 2 (tagline text, start of form) */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                          &quot;image    form&quot;&lt;/span&gt;&lt;span&gt; /* row 3 (image, form continues) */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                          &quot;address  form&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;/* row 4 (address, end of form) */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* assign each element to  a unique area on our grid */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  h1&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		grid-area&lt;/span&gt;&lt;span&gt;: header;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  p&lt;/span&gt;&lt;span&gt;.tagline-text&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    grid-area&lt;/span&gt;&lt;span&gt;: tagline;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  img&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    grid-area&lt;/span&gt;&lt;span&gt;: image;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  form&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    grid-area&lt;/span&gt;&lt;span&gt;: form;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .address-container&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    grid-area&lt;/span&gt;&lt;span&gt;: address;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  @media&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;max-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .outer-container&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      /* rearrange our grid on mobile, now using a 1 column layout */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      grid-template-areas&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;image&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                           &quot;header&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                           &quot;tagline&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                           &quot;form&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                           &quot;address&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nice! Not only can we define the positioning of elements however we want, but we even have an expressive explanation of the layout in our CSS using area names.&lt;/p&gt;&lt;p&gt;Of course, we&apos;ll need a few finishing touches to get all the spacing right (height of each row in our 2 column grid, spacing between the columns, etc.). So, if you&apos;re curious how the entire mockup was accomplished, you can check out a production-ready implementation &lt;a href=&quot;https://github.com/GTBitsOfGood/bog-web/blob/master/src/routes/contact.svelte&quot;&gt;on our GitHub repo for bitsofgood.org&lt;/a&gt; 😁&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Accessibility note:&lt;/strong&gt; CSS grid changes the &lt;strong&gt;visual&lt;/strong&gt; order of elements on the page, but not the actual order of elements in the DOM. So, when we wrap our address-container to the bottom on mobile, it still appears &lt;strong&gt;above&lt;/strong&gt; the form element in the underlying HTML. This shouldn&apos;t be much of an issue in our case, but before you start reordering text boxes on our own site, make sure this won&apos;t confuse screen readers!&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;2. When you want things &lt;em&gt;kind of&lt;/em&gt; centered&lt;/h2&gt;&lt;p&gt;I&apos;ve run into this a few times trying to make a nice container of &quot;cards,&quot; dynamically wrapping to a new row depending on the screen width. This conundrum is awkward to explain with words, so let&apos;s understand the problem visually:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/i/a7e3ys8qfn3llj31c2wk.gif&quot; alt=&quot;card-squoosh&quot; /&gt;&lt;/p&gt;&lt;p&gt;Okay, so our container of cards should always remain centered on the page (notice the white space on either side of our card grid). However, each &lt;em&gt;row&lt;/em&gt; of our container should start at the leftmost side and continue to the right (aka &lt;code&gt;flex-start&lt;/code&gt;). That means we can&apos;t simply use &lt;code&gt;justify-content: center&lt;/code&gt; here! That would center the item in the last row relative to the other rows, which breaks our illusion of a &quot;grid.&quot;&lt;/p&gt;&lt;p&gt;Oh, and we also want constant &quot;gap&quot; between each card, as well as a constant width for each card.&lt;/p&gt;&lt;p&gt;If you&apos;ve attempted this layout before, you can probably predict the &lt;code&gt;@media&lt;/code&gt; query adjustments we&apos;ll need. Here&apos;s a sample implementation, assuming we&apos;ll never need more than 3 columns. Click &quot;edit on CodePen&quot; if you wish to &lt;em&gt;squoosh&lt;/em&gt; that browser window.&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/VwvgoGJ&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;h3&gt;Why this implementation is bad&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;We need a &lt;code&gt;@media&lt;/code&gt; query to adjust our container width for each change in column-count. This requires some wacky mental math based on a) the width of each card, and b) the margin between elements. Adjusting the width of a card by even &lt;code&gt;1px&lt;/code&gt;, we&apos;ll need to run all these calculations again! And no, we can&apos;t use &lt;code&gt;calc()&lt;/code&gt; in a media query 😑&lt;/li&gt;&lt;li&gt;This doesn&apos;t consider screen sizes &lt;em&gt;below&lt;/em&gt; the width of a single card. We could add yet another &lt;code&gt;@media&lt;/code&gt; query to change this, but as we&apos;ll see soon, CSS Grid can remove this need entirely!&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Time to gridificate&lt;/h3&gt;&lt;p&gt;Now, let&apos;s see a sample implementation of the same layout with CSS grid:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.container-of-cards&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;grid&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* set our column widths with wrapping in place */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  grid-template-columns&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;repeat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;auto-fill&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;340&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  gap&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;60&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* center our columns relative to the container */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  justify-content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;center&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* add a max-width to prevent more than 3 columns */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  max-width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1200&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  margin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.card&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;120&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* no need to specify the width or margins! CSS grid does it all for us. */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Some big takeaways here...&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;grid-template-columns&lt;/code&gt; lets us create as many columns as we can fit in a given container. In our case, we want to create columns &lt;code&gt;340px&lt;/code&gt; wide, and &lt;code&gt;auto-fill&lt;/code&gt; our container with however many cards we have (wrapping to a new row as necessary).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;justify-content&lt;/code&gt; works a bit differently in Grid land. Rather than changing the alignment &lt;em&gt;within&lt;/em&gt; each row a-la Flexbox, it actually adjusts the alignment of the &lt;em&gt;entire Grid container&lt;/em&gt; relative to the page; exactly what we wanted! As an FYI, Grid uses a separate property called &lt;code&gt;justify-items&lt;/code&gt; to align items within a row.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Grid can assign the &lt;code&gt;gap&lt;/code&gt; between items in our container, essentially applying margins between each element. This is an &lt;em&gt;absolute godsend&lt;/em&gt; that&apos;s &lt;a href=&quot;https://bholmes.dev/#21whenyouwantthatgap&quot;&gt;worth explaining in its own section&lt;/a&gt;!&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h4&gt;Considering screen widths below &lt;code&gt;340px&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;As mentioned previously, flexbox usually needs an extra &lt;code&gt;@media&lt;/code&gt; query to shrink our layout for smaller screen sizes. Thankfully, CSS offers yet another magical operator for this: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/minmax&quot;&gt;&lt;strong&gt;minmax&lt;/strong&gt;&lt;/a&gt; ✨&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.container-of-cards&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  grid-template-columns&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;repeat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;auto-fill&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;minmax&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;max-content&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;340&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Breaking this down: the first parameter represents the minimum width each grid item can have, and the second represents the maximum width we should try to reach. In our case, we want cards to be &lt;code&gt;340px&lt;/code&gt; wide if possible, but shrink to 100% the width of the screen for layouts &amp;lt; &lt;code&gt;340px&lt;/code&gt; wide. In Grid land, we use &lt;code&gt;max-content&lt;/code&gt; to represent this minimum.&lt;/p&gt;&lt;h3&gt;2.5 When you want that &quot;gap&quot;&lt;/h3&gt;&lt;p&gt;This is a frustrating one: we want to apply a constant margin &lt;em&gt;between&lt;/em&gt; each element in a row, but not a margin at the start and end of the row.&lt;/p&gt;&lt;p&gt;Here&apos;s an example of the problem, taken from the &lt;a href=&quot;https://bitsofgood.org/projects&quot;&gt;Bits of Good projects page&lt;/a&gt;:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/i/8vesfhjfr9fok60pybgi.jpg&quot; alt=&quot;Screenshot of Bits of Good projects page&quot; /&gt;&lt;/p&gt;&lt;p&gt;With our old-guild flex knowledge, we might end up with this rough layout:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;outer-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;card&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;...card 1 content&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;card&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;...card 2 content&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .card&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;340&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /* add margins to either side of each card, totalling to a 60px &quot;gap&quot; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    margin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This &lt;em&gt;almost&lt;/em&gt; gets us where we want, but we end up with this awkward margin on either side of the flexbox. With a header in place, our layout gets a little out-of-wack.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/i/w8kjufj96mqijt447y4b.jpg&quot; alt=&quot;Screenshot showing inset margins on card layout&quot; /&gt;&lt;/p&gt;&lt;p&gt;If there are other sections on our page following the &lt;code&gt;max-width: 1200px&lt;/code&gt; rule, this margin issue will get even more obvious!&lt;/p&gt;&lt;p&gt;We have a few options to remedy this...&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Swallow up that offset with some negative margins on either side of our &lt;code&gt;outer-container&lt;/code&gt;. This is an okay band-aid to our issue, but it&apos;s not a super readable solution. For example, what if we want a margin around our grid of cards for mobile layouts, and no margin for wider layouts? We could write this as &lt;code&gt;margin: -30px&lt;/code&gt; -&amp;gt; &lt;code&gt;margin: 0&lt;/code&gt;, but that&apos;s &lt;a href=&quot;https://daverupert.com/2017/03/thoughts-on-negative-margins/&quot;&gt;not super readable / doesn&apos;t spark joy in some devs&apos; opinions&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Scrap the margin and use &lt;code&gt;margin-right: 60px&lt;/code&gt; on every card. This requires some &lt;code&gt;nth-child&lt;/code&gt; acrobatics to remove the margin on the last item in each row, which only gets worse as the number of columns changes 💀&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;...Or, we can just use Grid!&lt;/strong&gt; As we saw in our last solution, Grid allows you to define the gap between elements from the parent container. You can even define a separate gap between rows and columns.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.container-of-cards&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  display&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;grid&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* gap between rows */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  row-gap&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;60&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* gap between columns in a row */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  column-gap&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;120&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* same card setup as our last example */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  grid-template-columns&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;repeat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;auto-fill&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;minmax&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;max-content&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;340&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;3. When you want that collage effect&lt;/h2&gt;&lt;p&gt;This is a hallmark CSS Grid feature you&apos;ve probably seen by now, but it&apos;s definitely worth repeating.&lt;/p&gt;&lt;p&gt;Say you&apos;re making a photo gallery of some kind. Instead of making rows of cards with uniform widths and heights, you want to add some snazzy &lt;em&gt;dynamics&lt;/em&gt; to emphasize your favorite pics 📸&lt;/p&gt;&lt;p&gt;Here&apos;s one of my favorite examples of this end goal:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/i/sippj1hyzza3478kq608.gif&quot; alt=&quot;Squishing collage of butterflies&quot; /&gt;&lt;/p&gt;&lt;p&gt;I could attempt this layout with flexbox... but I don&apos;t think 1000 lines of &lt;code&gt;div&lt;/code&gt; adjustments and &lt;code&gt;@media&lt;/code&gt; queries is very productive. In any case, you&apos;d probably shoot down your designer&apos;s dreams entirely in a pre-Grid society.&lt;/p&gt;&lt;p&gt;You can &lt;a href=&quot;https://codepen.io/stacy/pen/xjRyda&quot;&gt;see the completed Pen&lt;/a&gt; to absorb all the Griddy goodness. The biggest takeaway I want to pull out here is Grid&apos;s concept of &quot;span:&quot;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.container&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* make a gridified container with 4 columns,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  each 25% of the container width (denoted by fr / fractional units) */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  grid-template-columns&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;repeat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;fr&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* don&apos;t specify how many rows (let everything auto-wrap) */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt;.thicc&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* let this image span 2 columns (wider than it is tall),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  starting at the first column */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  grid-column&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; / span &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  grid-row&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt;.tall&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  grid-column&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* let this image span 2 rows (taller than it is wide),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  starting at the third row */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  grid-row&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt; / span &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Some big takeaways here...&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;Grid lets you specify column widths using &quot;factional units,&quot; or &lt;code&gt;fr&lt;/code&gt;s. These are basically identify to &lt;code&gt;flex&lt;/code&gt; units, except the &lt;em&gt;container&lt;/em&gt; is charge of specifying them. This saves a lot of trouble when you want every element to obey a common unit.&lt;/li&gt;&lt;li&gt;You can allow each element on the grid to follow the specified rows and columns with no modification (each will span 1 column, 1 row by default), or...&lt;/li&gt;&lt;li&gt;You can add some &lt;code&gt;span&lt;/code&gt; styling for spicy dynamics 🌶. This gives us a neat collage effect.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;There are countless examples of Grid-heavy layouts to expand your realm of stylesheet possibilities. &lt;a href=&quot;https://codepen.io/collection/XRRJGq/&quot;&gt;This collection of Pens&lt;/a&gt; is a great place to start!&lt;/p&gt;&lt;h2&gt;Thanks for reading!&lt;/h2&gt;&lt;p&gt;I hope this post got your gears turning on applying Grid to your own applications. I will confess, adopting Grid &lt;a href=&quot;https://caniuse.com/#feat=css-grid&quot;&gt;tends to break layouts on IE 11&lt;/a&gt;, so make an informed decision before applying this to production applications!&lt;/p&gt;&lt;p&gt;Also, expect some more posts from me over the next few months. I finally graduated college, which I have... &lt;a href=&quot;https://twitter.com/BHolmesDev/status/1258771501647646722?s=20&quot;&gt;mixed feelings about to say the least&lt;/a&gt;. Now, I&apos;m trying to pump out quarantine-ridden content before I start my first &lt;em&gt;adult job.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;More to come soon.&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>How you can (sort of) write SASS @mixins in plain CSS</title><link>https://bholmes.dev/blog/how-you-can-sort-of-write-sass-mixins-in-plain-css/</link><guid isPermaLink="true">https://bholmes.dev/blog/how-you-can-sort-of-write-sass-mixins-in-plain-css/</guid><description>CSS variables are super powerful. Let&apos;s see how they can make repeated styles less DRY</description><pubDate>Fri, 08 May 2020 22:13:48 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Trigger warning:&lt;/strong&gt; I may use SASS and SCSS interchangeably throughout this post. You&apos;ve been warned&lt;/em&gt; 😈&lt;/p&gt;&lt;p&gt;You probably clicked on this post thinking &quot;wait, you can&apos;t write mixins in plain CSS. What kind of clickbait is this?&quot;&lt;/p&gt;&lt;p&gt;Let me admit something right off the bat: this post &lt;em&gt;does not&lt;/em&gt; show a one-to-one solution to replicate everything amazing about mixins. It&apos;s not like there&apos;s some magical corner of CSS to &lt;strong&gt;ENGAGE SASS MODE.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://media1.tenor.com/images/faf7ef24e333a644891ae19a07da3529/tenor.gif?itemid=5564441&quot; alt=&quot;Plankton (from Spongebob) whipping a krabby patty into maximum overdrive&quot; /&gt;&lt;/p&gt;&lt;p&gt;So yes, the pattern I&apos;m about to detail isn&apos;t a &lt;em&gt;direct&lt;/em&gt; translation of SASS / Stylus / LESS mixins. Still, it will help you overcome a problem that makes you reach for mixins in the first place:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;You have some CSS you want to reuse in a bunch of places&lt;/li&gt;&lt;li&gt;You want to pass some &lt;strong&gt;parameters&lt;/strong&gt; to that reusable CSS to adjust its behavior&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;Alright, expectations lowered. What do ya got?&lt;/h2&gt;&lt;p&gt;Well, it all starts with our friend, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties&quot;&gt;CSS variables&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;If you haven&apos;t used them before, CSS variables are a lot like SASS variables; you can store anything you want in them, and you can use them in any ruleset you define. However, these variables are &lt;em&gt;extra powerful&lt;/em&gt;. Rather than being a value you define &lt;strong&gt;once&lt;/strong&gt; and use everywhere, CSS variables can be reassigned at &lt;strong&gt;any level of the cascade&lt;/strong&gt;. SASS details this distinction &lt;a href=&quot;https://sass-lang.com/documentation/variables&quot;&gt;on their own documentation&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;That kind of power let&apos;s you pull off something like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;--theme-color: red&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;I&apos;m a red paragraph!&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;--theme-color: blue&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;I&apos;m a blue paragraph!&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--theme-color&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, each paragraph will have a different color, depending on the variable value assigned by the parent &lt;code&gt;div&lt;/code&gt;. Nice!&lt;/p&gt;&lt;h2&gt;A concrete example&lt;/h2&gt;&lt;p&gt;Say you have some social links on your site. You want all of these links to have a uniform layout, but you want to adjust the coloring to match the site you&apos;re linking to. In this case, we have two links to consider:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/i/nxbwv2mdv6e4vzt5xuz2.gif&quot; alt=&quot;Demo of two links with different colors (Twitter and GitHub)&quot; /&gt;&lt;/p&gt;&lt;p&gt;There&apos;s a bit more to this example than meets the eye. Notice each link has a different color in not one, but &lt;em&gt;four places&lt;/em&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The text &lt;code&gt;color&lt;/code&gt;&lt;/li&gt;&lt;li&gt;The SVG icon&apos;s &lt;code&gt;fill&lt;/code&gt;&lt;/li&gt;&lt;li&gt;The link &lt;code&gt;border&lt;/code&gt;&lt;/li&gt;&lt;li&gt;The &lt;code&gt;background-color&lt;/code&gt; when you hover&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Without variables, this would be extremely annoying / not-DRY. We&apos;d have to write four rulesets (including nested styling for the icon) for every new color we add 💀&lt;/p&gt;&lt;p&gt;If you&apos;re cool enough to use a preprocessor, you could truncate your styles using a handy mixin. It &lt;a href=&quot;https://www.youtube.com/watch?v=TLGWQfK-6DY&quot;&gt;goes a little something like this&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@mixin&lt;/span&gt;&lt;span&gt; color-link&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$color&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;$color&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  border&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; solid&lt;/span&gt;&lt;span&gt; $color&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* color the nested icon */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  svg&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fill&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;$color&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* change the background color on hover */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;amp;&lt;/span&gt;&lt;span&gt;:hover&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    background-color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;$color&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, if we ever have a new link color to add, we can write a nice one-liner in a new selector:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.twitter-link&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  @include&lt;/span&gt;&lt;span&gt; color-link&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;#1fa0f2&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Here&apos;s a Pen to show this solution in action:&lt;/strong&gt;&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/Yzyavop&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;h3&gt;Alright, but plain CSS can&apos;t do that...&lt;/h3&gt;&lt;p&gt;That&apos;s where you&apos;re &lt;em&gt;wrong!&lt;/em&gt; Though CSS lacks our nifty &lt;code&gt;@include&lt;/code&gt; syntax, we can still pass variables to a &lt;code&gt;color-link&lt;/code&gt; ruleset in a similar way.&lt;/p&gt;&lt;p&gt;Let&apos;s start with a raw example, no variables applied:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* change our mixin to a CSS class */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;.color-link&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* replace each $color reference with a hardcoded val for now */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#1fa0f2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  border&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; solid&lt;/span&gt;&lt;span&gt; #1fa0f2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/* CSS can&apos;t do nested rulesets, so we gotta separate these out */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;.color-link&lt;/span&gt;&lt;span&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; svg&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fill&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#1fa0f2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;.color-link:hover&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  background-color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#1fa0f2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;white&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;.color-link:hover&lt;/span&gt;&lt;span&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; svg&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fill&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;white&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We did a couple things here:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;We turned our &lt;code&gt;color-link&lt;/code&gt; mixin into a plain ole&apos; CSS class&lt;/li&gt;&lt;li&gt;We got rid of our nesting syntax since CSS still can&apos;t do that (&lt;a href=&quot;https://tabatkins.github.io/specs/css-nesting/&quot;&gt;but it could be coming soon!&lt;/a&gt;)&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;Now, we&apos;re ready to introduce some variable magic.&lt;/strong&gt; ✨&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;.color-link&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  /* replace our hardcoded vals with a variable reference */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--color&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  border&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt; solid&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--color&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;.color-link&lt;/span&gt;&lt;span&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; svg&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fill&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--color&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;.color-link:hover&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  background-color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--color&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;white&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;.color-link:hover&lt;/span&gt;&lt;span&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; svg&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  fill&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;white&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And finally, we&apos;ll rewrite our &lt;code&gt;twitter&lt;/code&gt; and &lt;code&gt;github&lt;/code&gt; classes from before:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.twitter-link&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#1fa0f2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.github-link&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#24292D&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Boom.&lt;/em&gt; With CSS variables, we can just assign a value to our &lt;code&gt;color&lt;/code&gt; variable in whatever CSS selector we choose. As long as we apply either of these guys alongside our &lt;code&gt;color-link&lt;/code&gt; class...&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;color-link twitter-link&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;...&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Our &lt;code&gt;color-link&lt;/code&gt; &quot;mixin&quot; will apply the appropriate colors where we need them!&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Here&apos;s another CodePen to see this working:&lt;/strong&gt;&lt;/p&gt;&lt;p&gt; &lt;span&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/LYpdMyr&quot;&gt;See the Pen&lt;/a&gt; by Benjamin Holmes (&lt;a href=&quot;https://codepen.io/bholmesdev&quot;&gt;@bholmesdev&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io/&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt; &lt;/p&gt; &lt;h2&gt;Yes, there are still limitations&lt;/h2&gt;&lt;p&gt;This definitely makes your plain CSS stylesheets more DRY, but it fails to address some funkier use cases.&lt;/p&gt;&lt;p&gt;For example, SASS can pull off conditionals and looping inside its mixins. This can let you, say, pass true / false booleans as parameters to switch between styles.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* arbitrary example, applying different layout mixins depending on browser support */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@mixin&lt;/span&gt;&lt;span&gt; grid-if-supported&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$grid&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    @if&lt;/span&gt;&lt;span&gt; $grid&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        @include&lt;/span&gt;&lt;span&gt; crazy-grid-layout&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;@else &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        @include&lt;/span&gt;&lt;span&gt; crazier-flexbox-layout&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Inspired by a rundown of magical mixin features &lt;a href=&quot;https://scotch.io/tutorials/how-to-use-sass-mixins&quot;&gt;found here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;We also have to modify our HTML by applying more classes. Depending on who you ask, this defeats part of a mixin&apos;s sexiness. Mixins can be dropped into any existing ruleset you already have, without needing to create new classes or rulesets.&lt;/p&gt;&lt;p&gt;I agree with this somewhat, but this post should at least show off how powerful CSS variables really are. They can even store complicated styles with spacing and commas, like &lt;code&gt;--crazy-padding: 12px 12px 0 0&lt;/code&gt; or &lt;code&gt;--dope-box-shadow: 1px 2px 3px #abcabc&lt;/code&gt;. The same can&apos;t be said for SASS mixin parameters!&lt;/p&gt;&lt;h2&gt;Thanks for reading!&lt;/h2&gt;&lt;p&gt;This post is part of my hard-headed goal to ditch CSS preprocessors. I definitely miss nested styles... but the power of CSS variables, combined with literal &lt;em&gt;microseconds&lt;/em&gt; of build time whenever I save, make me happy enough with raw CSS these days 😁&lt;/p&gt;&lt;p&gt;In case you&apos;re interested, I have another post taking a deeper dive into CSS variables, exploring how you can down on JS style manipulation. &lt;a href=&quot;https://dev.to/bholmesdev/how-using-css-variables-cut-down-on-my-javascript-dc4&quot;&gt;Check it out here!&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Also, expect some more posts over the next few months. I finally graduated college, which I have... &lt;a href=&quot;https://twitter.com/BHolmesDev/status/1258771501647646722?s=20&quot;&gt;mixed feelings about to say the least&lt;/a&gt;. This month should be nothing but pumping out quarantine-ridden content before I start my first &lt;em&gt;adult job.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;More to come soon.&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>The power of SvelteJS: building an animated image carousel in &lt;30 lines of code ✨</title><link>https://bholmes.dev/blog/making-an-animated-image-carousel-the-easy-way-with-svelte/</link><guid isPermaLink="true">https://bholmes.dev/blog/making-an-animated-image-carousel-the-easy-way-with-svelte/</guid><description>Let&apos;s make a carousel of images that fade between each other, all in 30 lines of code.</description><pubDate>Tue, 07 Apr 2020 20:01:36 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;Ah image carousels... the classic web dev conundrum. Back in the dark ages (so like 5 years ago), you&apos;d be reaching for bootstrap faster than you could spell &quot;bower.&quot; But now, with all the crazy component-based frameworks at our disposal, we might as well build one with our bare hands.&lt;/p&gt;&lt;p&gt;I&apos;ve build carousels in a couple settings (React, Angular 1.x, vanilla ES6) with varying degrees of simplicity, but nothing compares to a neat trick I found using &lt;a href=&quot;https://svelte.dev/&quot;&gt;Svelte&lt;/a&gt;. I&apos;ll start by walking through a solution you can use in any framework you choose, then cover the &lt;em&gt;super slick&lt;/em&gt; solution I found. This article should help whether you&apos;re already using Svelte, or you&apos;re considering it for your next web project. Since some Svelte newbies might find themselves here, I&apos;ll try to explain any Svelte-isms that pop up. Onwards!&lt;/p&gt;&lt;h2&gt;What we&apos;ll be building&lt;/h2&gt;&lt;p&gt;Let&apos;s see what kind of carousel we&apos;re talking here.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/bog/image/upload/v1586276527/blog/bog-logo-showdev/ewltvx3yuyguhue7qpi8.gif&quot; alt=&quot;Finished clickable image carousel&quot; /&gt;&lt;/p&gt;&lt;h3&gt;The behavior we want to see&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;A simple &quot;next&quot; button should show us the next image. We should also wrap around to the beginning when we reach the end of our carousel.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Images should fade &lt;em&gt;between&lt;/em&gt; each other. This means we can&apos;t just change the image&apos;s &lt;code&gt;src&lt;/code&gt; everytime we hit &quot;Next.&quot; There&apos;s a short in-between state where &lt;strong&gt;two images&lt;/strong&gt; are showing, one fading out while the other fades in.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The animation shouldn&apos;t break when we click really fast!&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;TLDR&lt;/h2&gt;&lt;p&gt;I get it. You&apos;re excited and what to copy / paste some working code into your project. Well, head over here to see a sandbox of the solution running with a nice browser preview!&lt;/p&gt;&lt;p&gt;You can also skip down to &lt;strong&gt;Using a Svelte-y approach&lt;/strong&gt; to understand how this works.&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; the images may load somewhat slowly. I&apos;m using picsum.photos to generate random images, but they should load faster if you&apos;re using static assets in your own project!&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://svelte.dev/repl/78bee610166a486a9304b9bfbeb77887?version=3.20.1&quot;&gt;🚀 Check out the finished REPL&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;The framework-agnostic approach: flip-flop between image elements&lt;/h2&gt;&lt;p&gt;Before jumping into the real Svelte magic, let&apos;s see how we&apos;d approach a carousel traditionally. I include this example since it&apos;s framework agnostic (&lt;a href=&quot;https://codepen.io/bholmesdev/pen/abOeVMY&quot;&gt;easily made with vanilla JavaScript too!&lt;/a&gt;), and similar to carousel tutorials you may have seen before. Still, we&apos;ll implement the algorithm using Svelte as a demo 🕺&lt;/p&gt;&lt;p&gt;In short, we need to take care of that annoying &quot;in-between&quot; state where two images are showing during the fade animation. Naturally, we should probably add two images to our markup:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/static/image_1.jpg&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/static/image_2.jpg&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&apos;s also assume we&apos;re keeping track of our image carousel URLs in a big JavaScript array. That may look something like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; carouselImages&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &apos;/static/image_1.jpg&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &apos;/static/image_2.jpg&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &apos;/static/image_3.jpg&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we need to figure out the logic to set the image sources correctly every time &quot;Next&quot; is clicked. This can get a little hairy, so let&apos;s walk through how our little algorithm will work:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;The first image element is showing the first image in our array. The second image is hidden.&lt;/li&gt;&lt;li&gt;When we click &quot;next,&quot; we set the source of the second image to the &lt;em&gt;next&lt;/em&gt; image in our array (so image 2). Then, we animate that image into view while the other animates out of view.&lt;/li&gt;&lt;li&gt;The second image element is showing the second image in our array. The first image is hidden.&lt;/li&gt;&lt;li&gt;When we click &quot;next,&quot; we change the first image source from &lt;strong&gt;the first image in the array (index 0)&lt;/strong&gt; to the &lt;strong&gt;third image in the array (index 2)&lt;/strong&gt; so that it shows the next image in the sequence. In other words, we bump up the index by 2. Then, we animate the first image element into view while the other animates out of view.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;... continue the pattern, bumping up the index of the &lt;em&gt;hidden&lt;/em&gt; image by 2 every time it gets displayed.&lt;/p&gt;&lt;p&gt;Here&apos;s a visual demonstration of how all that works!&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/bog/image/upload/v1586276545/blog/bog-logo-showdev/zehqkroq8cxvudz3xhhm.gif&quot; alt=&quot;Visual demo of changing the image source&quot; /&gt;&lt;/p&gt;&lt;h3&gt;The code&lt;/h3&gt;&lt;p&gt;If you&apos;ve worked with component-based frameworks before, you&apos;ve probably identified the state variables we need to keep track of:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;firstIndex&lt;/code&gt;&lt;/strong&gt; The array index for the first image &lt;code&gt;src&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;secondIndex&lt;/code&gt;&lt;/strong&gt; The array index for the second image &lt;code&gt;src&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;showFirst&lt;/code&gt;&lt;/strong&gt; A boolean for which image is currently displaying. In this case, &lt;code&gt;true&lt;/code&gt; means we should show the first image, and &lt;code&gt;false&lt;/code&gt; for the second&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Since this article&apos;s all about Svelte, I&apos;ll be using that framework to create and update these guys. But don&apos;t worry! This should be a pretty easy transition from your current framework of choice.&lt;/p&gt;&lt;h3&gt;Initial setup&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; carouselPhotos&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&apos;https://picsum.photos/300/200?random=1&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&apos;https://picsum.photos/300/200?random=2&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&apos;https://picsum.photos/300/200?random=3&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&apos;https://picsum.photos/300/200?random=4&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  // state variables (yes, just regular ole &quot;let&quot; variables!)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	let&lt;/span&gt;&lt;span&gt; firstIndex &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	let&lt;/span&gt;&lt;span&gt; secondIndex &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; carouselPhotos.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	let&lt;/span&gt;&lt;span&gt; showFirst &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; next&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		// logic goes here!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{carouselPhotos[firstIndex]}&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{carouselPhotos[secondIndex]}&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; on:click&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{next}&lt;/span&gt;&lt;span&gt;&amp;gt;Next!&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To start, we&apos;ve created our state variables, carousel &lt;code&gt;src&lt;/code&gt; URLs, all the markup we&apos;ll need, and a simple button click handler to jump to the next image.&lt;/p&gt;&lt;p&gt;Also, notice that &lt;code&gt;secondIndex&lt;/code&gt; starts at the &lt;em&gt;end&lt;/em&gt; of the array. This is because we want our second index to start one image &lt;em&gt;behind&lt;/em&gt; our first, so when bump up its index by 2, it becomes the next image in our array. Since we&apos;ll be allowing our indices to wrap to the beginning, we&apos;ll consider the last image to be the one before our first.&lt;/p&gt;&lt;h3&gt;Show and hide images on click&lt;/h3&gt;&lt;p&gt;Now, let&apos;s write our handy algorithm using code:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; next&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (showFirst) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // if the first image is showing, make sure&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // the second image shows the next array element&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      secondIndex &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (secondIndex &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; carouselPhotos.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // vice versa if the second image is showing&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      firstIndex &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (firstIndex &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; carouselPhotos.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // flip-flop!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    showFirst &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; !&lt;/span&gt;&lt;span&gt;showFirst&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That wasn&apos;t too bad! Notice how Svelte just lets you manipulate state variables directly without any fancy hooks of &quot;data&quot; objects. Just modify regular ole JS variables, and the UI will &lt;em&gt;react&lt;/em&gt; ✨&lt;/p&gt;&lt;p&gt;Here&apos;s how we can use our variables in the markup:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;style&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  img {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    opacity: 0;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    transition: opacity 0.5s;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  .visible {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		opacity: 1;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;img class={showFirst ? &apos;visible&apos; : &apos;&apos;} src={carouselPhotos[firstIndex]} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;img class={!showFirst ? &apos;visible&apos; : &apos;&apos;} src={carouselPhotos[secondIndex]} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;All we need is a dynamically applied CSS class depending on our &lt;code&gt;showFirst&lt;/code&gt; variable, and a couple styles to modify the opacity for a nice fade in and out.&lt;/p&gt;&lt;h3&gt;Demos&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://svelte.dev/repl/ffb7c5824fbc45319f132d9f2a6823d8?version=3.20.1&quot;&gt;🚀 See the full solution running at this REPL&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://codepen.io/bholmesdev/pen/abOeVMY&quot;&gt;😎 See the solution using vanilla JS with few modifications!&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;Cool solution! Is it the best one though?&lt;/h3&gt;&lt;p&gt;So in the end, this code is ready-to-use in your project no problem. I have a few complaints with it though:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;We have to write out two image elements in our markup, which feels a little repetitive. This will get more noticeable as we add more classes, attributes, and inevitable container &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s around each of them.&lt;/li&gt;&lt;li&gt;We&apos;re using three state variables for one piece of functionality. This may not sound like a lot, but it hurts the readability of our solution a bit. I bet if you stumbled across this code in someone&apos;s repository and didn&apos;t have shiny GIFs to understand the algorithm, it would be pretty confusing on first sight.&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;Using a Svelte-y approach&lt;/h2&gt;&lt;p&gt;Now, let&apos;s start using some Svelte-specific magic. There&apos;s two approaches that I find cleaner than the image flip-flop: one that&apos;s pretty readable but a little longer, and another that&apos;s &lt;em&gt;super sleek&lt;/em&gt; with a little added magic.&lt;/p&gt;&lt;h3&gt;Simple array mapping&lt;/h3&gt;&lt;p&gt;So you&apos;re probably familiar with how to render a list of content to the page, whether that means a &lt;code&gt;.map&lt;/code&gt;, an &lt;code&gt;*ngRepeat&lt;/code&gt;, a &lt;code&gt;v-for&lt;/code&gt;... you name it. You&apos;re also probably familiar with conditionally rendering elements with an if statement (&lt;code&gt;{condition &amp;amp;&amp;amp; ...}&lt;/code&gt;, &lt;code&gt;*ngIf&lt;/code&gt;, &lt;code&gt;v-if&lt;/code&gt;, etc.). So what can we do with these powers combined?&lt;/p&gt;&lt;p&gt;Well, make an image carousel of course! Let&apos;s check out this little chunk of code:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; currentCarouselIndex &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; next&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  	currentCarouselIndex &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (currentCarouselIndex &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; carouselPhotos.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{#each carouselPhotos as photo, index}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	{#if index == currentCarouselIndex}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{photo}&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	{/if}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{/each}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; on:click&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{next}&lt;/span&gt;&lt;span&gt;&amp;gt;Next!&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Bam! Now we&apos;re iterating over all the photos in the carousel, then only showing the photo matching our current index. This means we only need one state variable total: &lt;code&gt;currentCarouselIndex&lt;/code&gt;. Thankfully, I don&apos;t think I need to explain how Svelte handles for loops and if statements here. It&apos;s very readable on its own 😁&lt;/p&gt;&lt;p&gt;But wait, &lt;strong&gt;what about our in-between state?&lt;/strong&gt; In most frameworks, the &quot;if&quot; statement doesn&apos;t just change the opacity of a given element. Rather, it decides whether that element gets thrown on the page &lt;em&gt;at all.&lt;/em&gt; So in this case, we&apos;d obliterate the previous image in our carousel before it even has time to fade away! Hm...&lt;/p&gt;&lt;h3&gt;Transitions to the rescue&lt;/h3&gt;&lt;p&gt;Luckily, Svelte has a solution for this already included in the library: &lt;a href=&quot;https://svelte.dev/docs#svelte_transition&quot;&gt;&lt;strong&gt;transition directives&lt;/strong&gt;&lt;/a&gt; ✨&lt;/p&gt;&lt;p&gt;At a basic level, here&apos;s how they work:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;When an element first gets thrown onto the page (based on some conditional if statement), it triggers a tiny JavaScript function that animates that element into view.&lt;/li&gt;&lt;li&gt;When that element gets removed from the page (based on the same conditional), it triggers another tiny JavaScript function to animate that element out of view. Then, &lt;em&gt;only once this animation finishes&lt;/em&gt;, the element gets removed from the document entirely.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;This lets us support the in-between state only when we need it! Here&apos;s a transition directive in action:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	import&lt;/span&gt;&lt;span&gt; { fade } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;svelte/transition&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{#each carouselPhotos as photo, index}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	{#if index == currentCarouselIndex}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; transition:fade&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{photo}&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	{/if}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{/each}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&apos;s it! All we have to do is import the transition we want to use and apply it to our image. Then, let Svelte handle the JavaScript for us to add and remove elements at the appropriate time.&lt;/p&gt;&lt;p&gt;Here&apos;s a new visualization of what&apos;s going on:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/bog/image/upload/v1586276561/blog/bog-logo-showdev/rpimrrgvbhwhsv9btbee.gif&quot; alt=&quot;Visual of swapping images with array mapping&quot; /&gt;&lt;/p&gt;&lt;p&gt;And yes, you can customize the transition to your heart&apos;s content. Svelte has &lt;a href=&quot;https://svelte.dev/tutorial/transition&quot;&gt;a great tutorial series on everything you can do with transitions&lt;/a&gt;. It&apos;s a little tricky to master, but you&apos;ll see that it&apos;s just as powerful as CSS transitions and keyframes!&lt;/p&gt;&lt;p&gt;In the end, this is my favorite solution. But I bet we can do it with even fewer lines of code...&lt;/p&gt;&lt;h3&gt;Modification: The array key trick&lt;/h3&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; kudos to the incredible &lt;a href=&quot;https://github.com/ArkaneMoose&quot;&gt;Rishov Sarkar&lt;/a&gt; for finding this trick!&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Yes, our solution is nice and readable, but it feels kinda silly to iterate over &lt;em&gt;all&lt;/em&gt; the photos in our carousel just to pick out 1 image to render. Ideally, we want to tell Svelte that each image is unique, and we should animate between them whenever the &lt;code&gt;src&lt;/code&gt; changes.&lt;/p&gt;&lt;p&gt;Luckily, there &lt;em&gt;is&lt;/em&gt; such a way to uniquely identify each image: &lt;strong&gt;keys&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;We now know that transitions will trigger based on a conditional. But according to the &lt;a href=&quot;https://svelte.dev/docs#animate_fn&quot;&gt;Svelte docs&lt;/a&gt;: &quot;An animation is (also) triggered when the contents of a &lt;a href=&quot;https://svelte.dev/docs#each&quot;&gt;keyed each block&lt;/a&gt; are re-ordered.&quot; This is meant to help with, say, simple todo applications, where you might want to animate-in newly added list elements. As long as you apply a &quot;key&quot; to each element in the list (similar to the &lt;code&gt;key&lt;/code&gt; prop in React), Svelte will know which elements are &quot;new&quot; between renders.&lt;/p&gt;&lt;p&gt;So how can we apply this to our carousel? Let me throw the solution at you:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;... everything from the last solution&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{#each [carouselPhotos[currentCarouselIndex]] as photo (currentCarouselIndex)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; transition:fade&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{photo}&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{/each}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Woah! What did we just change?&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;Most importantly / weirdly, we&apos;re now iterating over an array with &lt;em&gt;only one element.&lt;/em&gt; In this case, we&apos;re creating an array containing just the photo at our current index.&lt;/li&gt;&lt;li&gt;We add &lt;code&gt;(currentCarouselIndex)&lt;/code&gt; to the end of our &lt;code&gt;#each&lt;/code&gt; statement to assign a &quot;key&quot; to each item in our array. We  know that the index is a unique identifier for items in our array of photos, so we can use it as the key for our image element. Since there&apos;s only one element in the array total, we can use our state variable as the unique indentifier.&lt;/li&gt;&lt;li&gt;We remove our conditional statement, since Svelte already knows the image is unique based on our key!&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;This feels a little strange on the surface, but it&apos;s actually quite simple. At the start, we have an array of one element: the first photo with a &quot;key&quot; of 0 (our current index). When we hit &quot;next,&quot; we transition from showing &lt;code&gt;[carouselPhotos[0] (key 0)]&lt;/code&gt; to &lt;code&gt;[carouselPhotos[1] (key 1)]&lt;/code&gt;. To us, it looks like we created an entirely new array. But from Svelte&apos;s perspective, it looks like we &lt;em&gt;removed&lt;/em&gt; the first photo from our array (key 0) and &lt;em&gt;added&lt;/em&gt; the second photo at the same time (key 1). So, it applies a fade out for what we &quot;removed&quot; and a fade in for what we &quot;added.&quot;&lt;/p&gt;&lt;p&gt;So, we took our already short-and-sweet solution and made it even shorter / sweeter 🚀&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/bog/image/upload/v1586276568/blog/bog-logo-showdev/s2zkohnytpjiogdvbkbn.gif&quot; alt=&quot;noice&quot; /&gt;&lt;/p&gt;&lt;h3&gt;Demo&lt;/h3&gt;&lt;p&gt;Feel free to edit the sandbox code to try out both solutions. Also take a peak at the &quot;JS output&quot; tab in the preview window, which shows you &lt;em&gt;exactly&lt;/em&gt; what get code is running on the page. In under 200 lines, all of this magic is possible without any hidden libraries or imports 🔥&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://svelte.dev/repl/78bee610166a486a9304b9bfbeb77887?version=3.20.1&quot;&gt;🚀 See the full solution running at this REPL&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;That&apos;s it!&lt;/h2&gt;&lt;p&gt;In the end, all solutions presented here should be good enough for a production-ready site. But I hope this post taught you some neat tricks that Svelte offers out-of-the-box to create some pretty complex UIs in just a few lines. In the end, our &quot;key trick&quot; solution comes in at under 30 lines of HTML, CSS, and JS &lt;em&gt;combined!&lt;/em&gt;&lt;/p&gt;&lt;p&gt;We also applied this solution in the real world with great success. Check out &lt;a href=&quot;https://bitsofgood.org/&quot;&gt;the homepage&lt;/a&gt; for the Bits of Good organization, partnering Georgia Tech students with nonprofits to build seriously-useful web apps.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/bog/image/upload/v1586277201/blog/bog-logo-showdev/y6n4ssg0ihc0v9tfjaeh.gif&quot; alt=&quot;Bits of Good homepage animation&quot; /&gt;&lt;/p&gt;&lt;p&gt;You can check out the &lt;a href=&quot;https://github.com/GTBitsOfGood/bog-web/blob/master/src/components/landing/BigLogo.svelte&quot;&gt;source code for this component here&lt;/a&gt;. The markup may be a little hard to read since we&apos;re nesting images in a big SVG vector, but you&apos;ll notice the &quot;key trick&quot; getting used the same way we just learned 😁&lt;/p&gt;&lt;h2&gt;Before you go: thanks for staying strong&lt;/h2&gt;&lt;p&gt;I&apos;m writing this post in the thick of COVID-19 social distancing. You may be a student grieving over missed opportunities or graduations, a mother or father spending too much time with your kids, or a recently unemployed household trying to make ends meet.&lt;/p&gt;&lt;p&gt;This is definitely a difficult time for all of us, but if there&apos;s any silver lining, it&apos;s all of these online learning opportunities at our disposal. So I&apos;m glad you took the time to give this a read! If you&apos;ve never used Svelte before, I hope it inspires you to pick it up 😁&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>The web dev tools that helped me get s*** done in 2019</title><link>https://bholmes.dev/blog/the-web-dev-tools-that-helped-me-get-s-done-in-2019-plus-a-thank-you/</link><guid isPermaLink="true">https://bholmes.dev/blog/the-web-dev-tools-that-helped-me-get-s-done-in-2019-plus-a-thank-you/</guid><description>A rundown of my favorite web dev tools in 2019 feat. Netlify, Svelte, CSS vars, and Contraste</description><pubDate>Fri, 03 Jan 2020 20:13:06 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;Now that we&apos;re turning over a new year (a new decade even!), I wanted to take a minute to remember all the tools that made my life easier in 2019. &quot;Best of&quot; lists for web dev tend to summarize the technologies in general terms. So for this, I want to focus more on the projects I worked on this year, and how these technologies helped me do them. Onwards!&lt;/p&gt;&lt;h2&gt;Netlify&lt;/h2&gt;&lt;p&gt;I&apos;m probably not alone in absolutely &lt;em&gt;gushing&lt;/em&gt; over &lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt; all year. It&apos;s just so freaking easy to get from zero to deployed! In brief, Netlify is for deploying a website frontend, along with a ton of extra goodies: continuous deployment, support for hooks, custom domains with automatic SSL certificates... all wrapped in a gorgeous dashboard that&apos;s easy to navigate.&lt;/p&gt;&lt;p&gt;I deployed two pretty big projects over there: my portfolio website at https://bholmes.dev, and the landing page for our Bits of Good organization over on https://bitsofgood.org. I hadn&apos;t managed custom domains in my life before this, but Netlify walked me through every step of copying DNS configs and other network-y things. I also highly recommend allowing Netlify to manage your domain over your domain provider, since they make setting up subdomains and SSL certificates so much easier 😁&lt;/p&gt;&lt;h3&gt;Rapid testing on mobile Safari&lt;/h3&gt;&lt;p&gt;Bet you&apos;ve heard this one before: the website looks great on the &quot;mobile view&quot; in Chrome dev tools, but the layout completely breaks on the actual phone 🤦‍♂️&lt;/p&gt;&lt;p&gt;Well, Netlify actually has a solution for this: &lt;a&gt;manual deployment from the command line&lt;/a&gt;. Instead of pushing up a new git commit every time you want to move that &lt;code&gt;margin&lt;/code&gt; 5 pixels to the right, you can just push up your build directory at a URL of your choosing! This is perfect for testing a frontend before making a PR, and Netlify will make sure all your serverless functions, API proxies, and extra production setup get deployed as well.&lt;/p&gt;&lt;p&gt;This was pivotal for our &lt;a href=&quot;https://bitsofgood.org/&quot;&gt;Bits of Good homepage&lt;/a&gt;, where we had some magical CSS to reorganize our swooping vector graphics on mobile views. Also helped us &lt;a href=&quot;https://stackoverflow.com/questions/37112218/css3-100vh-not-constant-in-mobile-browser&quot;&gt;catch a screen sizing issue&lt;/a&gt; well before pushing a PR!&lt;/p&gt;&lt;h3&gt;Using Netlify Forms&lt;/h3&gt;&lt;p&gt;I just dipped my toes into this, but &lt;a href=&quot;https://docs.netlify.com/forms/setup/&quot;&gt;Netlify forms&lt;/a&gt; proved a super quick and easy solution to storing HTML form responses without a backend. This came in clutch for our new Bits of Good &quot;Contact Us&quot; page. Originally, we planned to make an API request once the form&apos;s submitted to &lt;code&gt;POST&lt;/code&gt; to a serverless function, which could then forward messages to our email. Not terrible, but sounds like a good 6-7 hours of work (we&apos;re bad at backends, so hold the I-could-do-this-in-negative-3-hours 😆).&lt;/p&gt;&lt;p&gt;Now with Netlify, here&apos;s all we have to do (for &lt;strong&gt;free&lt;/strong&gt; mind you!)&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Add &lt;code&gt;data-netlify&lt;/code&gt; to our &lt;code&gt;form&lt;/code&gt; element&lt;/li&gt;&lt;li&gt;Make sure each element in the form as a &lt;code&gt;name&lt;/code&gt; attribute (which it already should for accessibility reasons!)&lt;/li&gt;&lt;li&gt;Add a &lt;code&gt;name&lt;/code&gt; attribute to the form itself so we can see which form gets piped into Netlify&lt;/li&gt;&lt;li&gt;Profit 💰&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Boom! All form responses now get stored on Netlify, complete with pretty solid spam detection. We can also deploy our serverless function for email forwarding directly to Netlify, which already has hooks for detecting form submissions. Nice!&lt;/p&gt;&lt;h2&gt;Svelte v3&lt;/h2&gt;&lt;p&gt;I stumbled across this library at the end of last year, when it was the #1 write-in for the &lt;a href=&quot;https://2018.stateofjs.com/introduction/&quot;&gt;State of JS 2018&lt;/a&gt; survey. It was running on v2 at the time, but I was still blown away by it&apos;s mission statement: the magical disappearing framework ✨&lt;/p&gt;&lt;p&gt;Now, I&apos;ve built more projects with it than I can count! Here&apos;s the gist of why it&apos;s so awesome:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;It&apos;s a library that gets out of your way at runtime. Instead of having some &quot;virtual DOM&quot; to manage the page or a needless JQuery dependency, it just slaps the code you wrote onto the browser with zero added dependencies! It&apos;ll generate helper functions as needed of course, but &lt;a href=&quot;https://twitter.com/swyx/status/1172690454363672577&quot;&gt;the difference in production bundle size is staggering&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;You manage state without even realizing it. Instead of having a &lt;code&gt;setState&lt;/code&gt; function or something similar, you just change your JS variables directly... and Svelte knows how to update the page. There&apos;s some extra tooling to handle edge cases, but this is definitely the biggest time saver for me!&lt;/li&gt;&lt;li&gt;The templating is &lt;em&gt;super&lt;/em&gt; minimal. All you need to know is how to write a &lt;code&gt;for&lt;/code&gt; loop for lists of elements and how to insert JS variable values. There&apos;s a bunch of added goodies for binding and event handlers once you find the need.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;For a quick rundown on how easy Svelte is to learn and use, &lt;a href=&quot;https://dev.to/bholmesdev/why-sveltejs-may-be-the-best-framework-for-new-web-devs-205i&quot;&gt;check out my blog post&lt;/a&gt; exploring the tool from a teacher&apos;s perspective!&lt;/strong&gt;&lt;/p&gt;&lt;h3&gt;Svelte for quick projects&lt;/h3&gt;&lt;p&gt;Svelte has become my new go-to for getting small projects up and running fast. I&apos;ve been to a few hackathons at my university (&lt;a href=&quot;https://gfycat.com/thornyoblonganemone&quot;&gt;go Yellow Jackets!&lt;/a&gt;), and I use &lt;a href=&quot;https://github.com/bholmesdev/svelte-starter-template&quot;&gt;this Svelte template&lt;/a&gt; to start working on a web UI. I started reaching for Svelte over, say, React because of how easy it is to make new components. Each component is basically &quot;sandboxed&quot; in its own file, where you write vanilla HTML, CSS, and JS while having all your styles and scripts scoped to that component alone. It&apos;s almost like whipping up a CodePen, if you could compose CodePens to create a mega-Pen for your entire website 😁&lt;/p&gt;&lt;p&gt;One of my favorite highlights was for a hardware hackathon called &lt;a href=&quot;https://build.hack.gt/&quot;&gt;BuildGT&lt;/a&gt;. The premise: play a frame of bowling with a Roomba as the ball! The end product is something that never should have existed, but it was super fun to get everything talking to each other. We handled communication between the Roomba and the Kinect sensor (for fallen pin detection) via web sockets, but the icing on the cake was the instructional UI built on Svelte in a matter of hours.&lt;/p&gt;&lt;h4&gt;The hack&lt;/h4&gt;&lt;blockquote&gt;&lt;p&gt;■■■■■▣□□□□ 59% &lt;a href=&quot;https://x.com/hashtag/100DaysOfCode?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#100DaysOfCode&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Joined a team for the 12 hour hardware+software hackathon &lt;a href=&quot;https://x.com/hashtag/BuildGT?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#BuildGT&lt;/a&gt;. 41 projects total... And we managed to take 1st 🏆 ! Here&apos;s a demo of our hack in action: Roomba bowling feat. a raspberry pi, Kinect sensor, a web UI, and web sockets 🔥 &lt;a href=&quot;https://t.co/XIvsz3D2pN&quot;&gt;pic.twitter.com/XIvsz3D2pN&lt;/a&gt;&lt;/p&gt;— Ben Holmes (@BHolmesDev) &lt;a href=&quot;https://x.com/BHolmesDev/status/1102251893110333440?ref_src=twsrc%5Etfw&quot;&gt;March 3, 2019&lt;/a&gt;&lt;/blockquote&gt;

 &lt;h4&gt;The UI&lt;/h4&gt;&lt;blockquote&gt;&lt;p&gt;■■■■■▣□□□□ 59% &lt;a href=&quot;https://x.com/hashtag/100DaysOfCode?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#100DaysOfCode&lt;/a&gt; (part 2)&lt;br /&gt;&lt;br /&gt;Spent a lot of my time on the web UI. Kept it super simple with raw HTML and CSS, and used &lt;a href=&quot;https://x.com/sveltejs?ref_src=twsrc%5Etfw&quot;&gt;@sveltejs&lt;/a&gt; v3 to build it super quickly and change state based on web socket messages. Also grabbed those classic bowling alley anims 😆 &lt;a href=&quot;https://t.co/fA8gpNxgzk&quot;&gt;pic.twitter.com/fA8gpNxgzk&lt;/a&gt;&lt;/p&gt;— Ben Holmes (@BHolmesDev) &lt;a href=&quot;https://x.com/BHolmesDev/status/1102254956973826048?ref_src=twsrc%5Etfw&quot;&gt;March 3, 2019&lt;/a&gt;&lt;/blockquote&gt;

 &lt;h3&gt;Svelte for static sites&lt;/h3&gt;&lt;p&gt;Aside from getting out ideas quickly, Svelte has some tricks up its sleeve for static site generation too. We use this for our Bits of Good org website (&lt;a href=&quot;https://github.com/GTBitsOfGood/bog-web&quot;&gt;full repo here&lt;/a&gt;), built on Svelte and the &lt;a href=&quot;https://www.contentful.com/&quot;&gt;Contentful CMS&lt;/a&gt;. To help with routing and prerendering the actual static site, we&apos;re using the magical &lt;a href=&quot;https://sapper.svelte.dev/&quot;&gt;Sapper&lt;/a&gt; framework to manage it all for us. This acts a lot like &lt;a href=&quot;https://nextjs.org/&quot;&gt;NextJS&lt;/a&gt;, but offers some really nice benefits:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Management of server-side rendering to make that initial page load &lt;em&gt;fast.&lt;/em&gt; Similar to NextJS, you can specify all the API calls you want to fire before the page loads &lt;a href=&quot;https://sapper.svelte.dev/docs#Preloading&quot;&gt;like so&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Management of routing behind the scenes. It even lets you use a regular old &lt;code&gt;a&lt;/code&gt; tag to link to other routes on the site, rather than importing some special &lt;code&gt;Link&lt;/code&gt; component.&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Sapper also uses a build tool you can actually understand: Rollup!&lt;/h4&gt;&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;If you&apos;re wondering what a CMS is,&lt;/strong&gt; it&apos;s an external tool for storing all the text and images used by your website. This comes in handy working with non-techy team members who want to edit the content of the site, but don&apos;t know how to code. Instead of asking a developer, they can just open a web portal and edit the content themselves! &lt;a href=&quot;https://wordpress.org/&quot;&gt;Headless Wordpress&lt;/a&gt; is one of the most popular examples of this.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;This lightweight build tool came in handy for managing our Contentful CMS setup. Rather than wrestling with webpack for days on end, Rollup was easy enough that we could set up a plugin from scratch! The docs walked us through every step of the process and led us to a pretty slick solution. All we do is import our text and image content from the CMS with an API call, convert markdown to HTML as necessary, and slap everything in a fat JSON at build time.&lt;/p&gt;&lt;p&gt;Now, whenever a component needs to include some content from the CMS, we can just import by that content&apos;s ID. For example, here&apos;s how we would fetch all of our nonprofit projects for our &lt;a href=&quot;https://bitsofgood.org/projects&quot;&gt;project highlights page&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; projects &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@contentful-entries/project&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Bam! Now &lt;code&gt;projects&lt;/code&gt; is an array of JavaScript objects with all the text and image URLs we need. As you can see, Rollup also helped us add an alias called &lt;code&gt;@contentful-entries&lt;/code&gt; to make content retrieval a little easier. For those curious about the nitty-gritty details, here&apos;s our &lt;a href=&quot;https://github.com/GTBitsOfGood/bog-web/blob/master/rollup-plugin-content-loader.js&quot;&gt;final RollupJS plugin&lt;/a&gt; ✨&lt;/p&gt;&lt;h2&gt;CSS variables / custom properties&lt;/h2&gt;&lt;p&gt;This was a big win for CSS when it hit browsers in 2018. In 2019, the feature&apos;s matured a ton and scored some pretty great browser adoption. Yes, IE is still the problem child... but there&apos;s &lt;a href=&quot;https://stackoverflow.com/questions/46429937/ie11-does-a-polyfill-script-exist-for-css-variables&quot;&gt;some good polyfills&lt;/a&gt; at this point if you need them 🙃&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/l929q16h8jztr2xiingk.png&quot; alt=&quot;Browser support for CSS variables&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Source: caniuse.com as of January 3, 2020&lt;/em&gt;&lt;/p&gt;&lt;p&gt;I&apos;ve started using these across the board to &lt;em&gt;finally&lt;/em&gt; ditch SCSS for managing colors, font sizes, margins, etc. These are also much more versatile than the variables you&apos;ll find in SCSS preprocessors. Here&apos;s the main benefit: &lt;strong&gt;instead of creating static variables at the global level, you can now create variables that only apply to the element getting styled.&lt;/strong&gt; In other words, these variables can exist at any level of the cascade, and dynamically update with the power of JS or CSS keyframes!&lt;/p&gt;&lt;p&gt;This was super useful when I moved some complex style calculations from JavaScript over to CSS with &lt;code&gt;calc()&lt;/code&gt; magic. Crucially, CSS variables let me make JavaScript variables visible in CSS using inline styles.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;To see CSS variables in action, &lt;a href=&quot;https://dev.to/bholmesdev/how-using-css-variables-cut-down-on-my-javascript-dc4&quot;&gt;check out my blog post&lt;/a&gt; on a practical application!&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;Contraste&lt;/h2&gt;&lt;p&gt;&lt;em&gt;Disclaimer: This is an app only available for Macs at the moment&lt;/em&gt;&lt;/p&gt;&lt;p&gt;This is more of a &quot;design&quot; tool than a pure &quot;web dev&quot; tool, but I use it so often that I can&apos;t help but mention it here.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://contrasteapp.com/&quot;&gt;Contraste&lt;/a&gt; is a dead simple tool for one thing only: checking the text contrast on a page to get an accessibility rating. This is measured by &quot;&lt;a href=&quot;https://www.werkbot.com/blog/ada-compliance-a-vs-aa-vs-aaa&quot;&gt;ADA compliance&lt;/a&gt;,&quot; which applies a score of A, AA, or AAA depending on your site&apos;s accessibility to the visually impaired. This all comes down to text contrast, which isn&apos;t as easy to eyeball as I thought!&lt;/p&gt;&lt;p&gt;I freaking love this tool just for how simple it is, and the fact that it&apos;s a global tool rather than a browser extension. It&apos;s actually exposed some accessibility concerns when going from designer mockup to live website, where colors on the web don&apos;t &lt;em&gt;quite&lt;/em&gt; match the beautiful colors in the mockup tool.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/9jshlsek63tu4nnhffb2.gif&quot; alt=&quot;Contraste color picker in action, with color adjustent to fix accessibility compliance&quot; /&gt;&lt;/p&gt;&lt;h2&gt;Before I go, thanks for all the support this year 😁&lt;/h2&gt;&lt;p&gt;I started this blog back in March with a quick little post: &lt;a href=&quot;https://dev.to/bholmesdev/why-im-using-surge-and-not-github-pages-4lf5&quot;&gt;Why I&apos;m using Surge and not GitHub pages&lt;/a&gt;. I threw it out there not really knowing what I was doing (even managed to write some technical inaccuracies on a 3 minute read 😆), and expected maybe a couple likes by the morning.&lt;/p&gt;&lt;p&gt;Then, I opened my profile to find over 100 followers and close to 1000 views. &lt;em&gt;Excuse me!? For this?&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/w8m10eiom3g48w3r9m6s.png&quot; alt=&quot;Meme of man blinking in disbelief&quot; /&gt;&lt;/p&gt;&lt;p&gt;To be honest, it was a major stroke of luck from sending the &lt;a href=&quot;https://twitter.com/BHolmesDev/status/1103437685140660224?s=20&quot;&gt;right tweet at the right time&lt;/a&gt; (thanks for the like &lt;a href=&quot;https://dev.to/emmabostian&quot;&gt;Emma Bostian!&lt;/a&gt;). If you&apos;re stuck trying to find readers on your own blog, all I can say is this: &lt;strong&gt;never stop writing. Never stop advertising yourself. And please, never stop learning.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;So, after this freak accident and many posts later, I want to thank everyone so much for supporting me. It really gave me the motivation to see my blog through and genuinely love teaching others about web development. I&apos;m apparently ending the year as one of the Top 500 authors on dev.to which is... a ridiculous milestone I&apos;ll never understand.&lt;/p&gt;&lt;p&gt;So, here&apos;s to many more posts and learning opportunities in 2020! Cheers 🥂&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>HTML5 tags - how do they work, and which ones should I use?</title><link>https://bholmes.dev/blog/html5-tags-how-do-they-work-and-which-ones-should-i-use/</link><guid isPermaLink="true">https://bholmes.dev/blog/html5-tags-how-do-they-work-and-which-ones-should-i-use/</guid><description>Whether you&apos;ve seen HTML or not, it may feel unclear when to use each element, or even what elements are available these days.</description><pubDate>Mon, 02 Dec 2019 23:01:19 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;&lt;strong&gt;Let&apos;s get down to the fundamentals.&lt;/strong&gt; Whether you&apos;ve seen HTML or not, it may feel unclear when to use each element, or even what elements are available these days. The following should be a helpful cheatsheet for web dev beginners and markup professionals alike!&lt;/p&gt;&lt;p&gt;&lt;em&gt;This article is a snippet from &lt;strong&gt;Bits of Good&apos;s web dev bootcamp.&lt;/strong&gt; If you like what you see, &lt;a href=&quot;https://www.notion.so/gtbitsofgood/Curriculum-ca431096426b4fd1968ac49121ff2fdb&quot;&gt;head over here&lt;/a&gt; to view the full course (with written resources, video walkthroughs, and project activities) for free!&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;Starting with definitions&lt;/h2&gt;&lt;p&gt;HTML, or &lt;em&gt;Hypertext Markup Language,&lt;/em&gt; is a language used to define the &quot;skeleton&quot; of a webpage. It isn&apos;t meant to include any bells and whistles, and it definitely won&apos;t look pretty on its own! It&apos;s simply meant to define &lt;em&gt;what&lt;/em&gt; gets shown on the page, from images to text and everything in between.&lt;/p&gt;&lt;p&gt;All modern browsers support the latest HTML standard: &lt;strong&gt;HTML5&lt;/strong&gt;. This adds a whole host of features to improve the structuring of webpages. Namely, it adds special elements to denote the &quot;layout&quot; of the page, like where the navigation bar is, where the footer is, and so on. This is super useful for visually impaired individuals that rely on screen readers to browse the Internet, since it&apos;s now easier than ever to explain all the landmarks on the page right from the HTML!&lt;/p&gt;&lt;h2&gt;Getting into syntax&lt;/h2&gt;&lt;p&gt;When writing HTML, you will be working with something known as a &lt;strong&gt;tag.&lt;/strong&gt; Basically, this is used to define some block of content you want to display on your website. There are many different tags you can use for different types of content, like blocks of text, images, headings, and so on.&lt;/p&gt;&lt;p&gt;For example, we can use the paragraph tag &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; to display a block of text like so:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  Hard work and no play makes Jack a dull paragraph.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that we need to right the tag twice: once to define where its content starts and again to define where it ends. In this case, it starts before our block of text (&lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;) and ends just after our text block (&lt;code&gt;&amp;lt;/p&amp;gt;&lt;/code&gt;). Note you need an added &lt;code&gt;/&lt;/code&gt; to denote a closing tag versus an opening tag. This whole block start to finish is called an &lt;strong&gt;element.&lt;/strong&gt;&lt;/p&gt;&lt;h3&gt;Attributes&lt;/h3&gt;&lt;p&gt;Along with declaring a tag, you can also add a set of &lt;strong&gt;attributes&lt;/strong&gt; to that tag for extra information. These are written using the format &lt;code&gt;attribute=&quot;value&quot;&lt;/code&gt; inside the opening tag.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;class&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt; attributes are the most common, which will be explained better &lt;a href=&quot;https://www.notion.so/gtbitsofgood/CSS-Overview-and-specificity-519611a09c684d85910f40a3e00fcb06&quot;&gt;once we start using CSS&lt;/a&gt;. Another example is declaring the language used by your page with &lt;code&gt;lang&lt;/code&gt; as shown below.&lt;/p&gt;&lt;h3&gt;The main wrapper&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt; lang&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;en-US&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;!-- More to come in here! --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is a basic wrapper you would always want to put around your HTML file. First, we create the base &lt;code&gt;html&lt;/code&gt; element and specify our page&apos;s language using &lt;code&gt;lang=&quot;en-US&quot;&lt;/code&gt;. Since anyone from Canada to Singapore may access the websites you build, it&apos;s best to specify the language so translation tools know what to expect!&lt;/p&gt;&lt;p&gt;Next, we add our body for all of our website&apos;s content to live. This is denoted by the &lt;code&gt;body&lt;/code&gt; element. Also notice the &lt;code&gt;&amp;lt;!-- … --&amp;gt;&lt;/code&gt; inside. This is how comments work in HTML, so whatever&apos;s inside those brackets won&apos;t be shown on the page.&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you use CodePen, each Pen automatically adds these &lt;code&gt;html&lt;/code&gt; and &lt;code&gt;body&lt;/code&gt; wrappers for you around whatever code you put inside the &quot;HTML&quot; box, complete with the language specification. So, no need to use it on every CodePen you create, but know it&apos;s an important concept when building website outside of this tool!&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;Important tags&lt;/h2&gt;&lt;p&gt;There are &lt;strong&gt;many&lt;/strong&gt; HTML tags worth knowing. You can find the full list &lt;a href=&quot;https://www.tutorialrepublic.com/html-reference/html5-tags.php&quot;&gt;here&lt;/a&gt;, but these are some highlights you should know for getting started 🏃‍♂️&lt;/p&gt;&lt;h3&gt;Tags for content&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; A paragraph block; the most general block when you want some text on the page&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; ... &lt;code&gt;&amp;lt;h6&amp;gt;&lt;/code&gt; A header block, with &lt;code&gt;h1&lt;/code&gt; being the highest level header, &lt;code&gt;h2&lt;/code&gt; being the second highest, and so on. A newspaper article is a solid example of this, where the article title would be an &lt;code&gt;h1&lt;/code&gt;, each section heading would be an &lt;code&gt;h2&lt;/code&gt;, and each subheading would be an &lt;code&gt;h3&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;ul&lt;/code&gt; / &lt;code&gt;ol&lt;/code&gt; A block for creating a list of content. Use a &lt;code&gt;ul&lt;/code&gt; when you want a bulleted (&lt;em&gt;u&lt;/em&gt; nordered) list and an &lt;code&gt;ol&lt;/code&gt; for a numbered (&lt;em&gt;o&lt;/em&gt; rdered) list&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;li&lt;/code&gt;, or &lt;em&gt;l&lt;/em&gt; ist &lt;em&gt;i&lt;/em&gt; tem, for each element inside of the list&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;dl&lt;/code&gt; A block for creating a more complex list of content. This works a lot like a &lt;code&gt;ul&lt;/code&gt; / &lt;code&gt;ol&lt;/code&gt;, but now each item can have a title &lt;em&gt;and&lt;/em&gt; a description as two separate blocks. Perfect for lists that have a header / body structure&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;dt&lt;/code&gt; The &quot;definition title,&quot; or the title of a list item&lt;/li&gt;&lt;li&gt;&lt;code&gt;dd&lt;/code&gt; The &quot;definition description,&quot; or the content of that list item &lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; No need for a wrapper around these two elements. Just place the &lt;code&gt;dd&lt;/code&gt; element below its associated title &lt;code&gt;dt&lt;/code&gt;, and the list item should look right! If you absolutely need to, it&apos;s safe to wrap these in a &lt;code&gt;div&lt;/code&gt; for styling purposes.&lt;/em&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; A wrapper around text within a paragraph or header. This is great for targeting certain lines or words in a text block for custom styling&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;&amp;lt;a href=&quot;https://penguin.club&quot;&amp;gt;&lt;/code&gt; A weblink to some other website or a page within your own website. The link is specified by the &lt;code&gt;href&lt;/code&gt; attribute&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; You can force links to open in a new tab instead of the current page by including the attribute &lt;code&gt;target=&apos;_blank&apos;&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;&amp;lt;img src=&quot;https://cute_cat.gif&quot; alt=&quot;gif of a cat eating ice cream&quot; /&amp;gt;&lt;/code&gt; An image, with the actual image file coming from the &lt;code&gt;src&lt;/code&gt; link and the &lt;code&gt;alt&lt;/code&gt; describing the image. You can read the &lt;code&gt;alt&lt;/code&gt; when hovering over an image or when using a screen reader&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; A button you can click. You&apos;ll use JavaScript to get buttons to actually do something, but we&apos;ll leave that as a mystery for now 😁&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Tags for the layout of that content&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; A page header, often with the site banner and navigation bar&lt;/li&gt;&lt;li&gt;&lt;code&gt;&amp;lt;footer&amp;gt;&lt;/code&gt; A page footer, often with important links that couldn&apos;t go anywhere else&lt;/li&gt;&lt;li&gt;&lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; The main content area of a page. Helpful to separate from the header and footer&lt;/li&gt;&lt;li&gt;&lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; The navigation bar of a webpage (aka that ribbon at the top with all the links to other pages on the site). Usually within the &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; A sub-portion of the main content. Good for dividing up, well, sections of the page. Yes, nesting sections within sections works as well!&lt;/li&gt;&lt;li&gt;&lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; A general way to define a container. You&apos;ll end up reaching for these when working on page layouts, trying to position content in certain places on the page&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;&lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; versus &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Generally, you&apos;ll use a &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; to group elements under common themes, like in a book report outline you&apos;d write in high school. A &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, meanwhile, is not used to convey this sort of separation. Take a news article as an example. We may say the article&apos;s content and the comments are two separate &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; elements. However, each comment, along with the container for like and dislike buttons, are just &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s.&lt;/p&gt;&lt;h2&gt;Try these elements yourself!&lt;/h2&gt;&lt;p&gt;Hop over to &lt;a href=&quot;https://codepen.io/&quot;&gt;codepen.io&lt;/a&gt; and make a new Pen. Start pasting some of the tags above into the HTML section to see what they display in the preview box!&lt;/p&gt;&lt;h3&gt;See these tags in action&lt;/h3&gt;&lt;p&gt;Now, let&apos;s take a look at a completed website to see how the HTML is structured. We&apos;ll be using your browser&apos;s &quot;inspector&quot; for this, and you can follow along by visiting the live site at https://gsg.surge.sh. I encourage you to open this tool on some of your favorite website as well to see how they structure their own markup.&lt;/p&gt;&lt;iframe src=&quot;https://www.youtube-nocookie.com/embed/I4FK_7pZJV8&quot; width=&quot;560&quot; height=&quot;315&quot; allowfullscreen&gt;  &lt;/iframe&gt;  &lt;h2&gt;Found this helpful?&lt;/h2&gt;&lt;p&gt;Great! This article is part of a &lt;em&gt;huge&lt;/em&gt; suite of resources used by the Bits of Good organization to teach newbies the ropes. If you want to...&lt;/p&gt;&lt;ul&gt;&lt;li&gt;💅 &lt;strong&gt;learn how to write better CSS with your bare hands&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;⚙️ &lt;strong&gt;get the hang of array functions in JS&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;📶 &lt;strong&gt;figure out how to talk to APIs&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;👩‍💻 &lt;strong&gt;build feature-rich frontends and backends using JS frameworks&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;🏃‍♂️ &lt;strong&gt;&lt;em&gt;and most anything else in the world of web dev&lt;/em&gt;,&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Check out the Bits of Good bootcamp. All activities, written resources, and projects are free for you to explore and share with others 😁&lt;/p&gt;&lt;p&gt;👉 You can access the curriculum &lt;a href=&quot;https://www.notion.so/gtbitsofgood/Curriculum-ca431096426b4fd1968ac49121ff2fdb&quot;&gt;here&lt;/a&gt;!&lt;/p&gt;&lt;p&gt;If you have any feedback, criticisms, or content suggestions, message us at hello@bitsofgood.org.&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>What I learned planning and building an MVP as a frontend dev at Peloton</title><link>https://bholmes.dev/blog/what-i-learned-planning-and-building-an-mvp-as-a-frontend-dev-at-peloton/</link><guid isPermaLink="true">https://bholmes.dev/blog/what-i-learned-planning-and-building-an-mvp-as-a-frontend-dev-at-peloton/</guid><description>As a frontend dev with little industry experience, these are some lessons learned in the project development process.</description><pubDate>Tue, 03 Sep 2019 14:57:01 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;Writing this, I just finished my summer internship at &lt;a href=&quot;https://onepeloton.com/&quot;&gt;Peloton Interactive&lt;/a&gt; 🥳 As I hang up my biker gang helmet, I wanted to talk about my biggest learning experience: working on a large-scale ReactJS project from concept to MVP presentation. I&apos;ll walk through the process of planning and building the project from start to finish, discussing the lessons I learned along the way.&lt;/p&gt;&lt;p&gt;Before you click away hearing the word &quot;internship,&quot; note this is a 100% real project that is deployed to production &lt;a href=&quot;https://onepeloton.com/careers&quot;&gt;right now&lt;/a&gt;!&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; This post touches both on the product planning process and some of my learnings with git and design. Since I don&apos;t know my audience&apos;s background writing this, I hope you find some takeaways on any front that speak to you!&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;So, what was the project?&lt;/h2&gt;&lt;p&gt;Simply put, to fix... this.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/6h7wnqu21z6zf9nl7dqp.gif&quot; alt=&quot;Scrolling through old Peloton careers page... for a very long time&quot; /&gt;&lt;/p&gt;&lt;p&gt;It&apos;s nice to see a lot of open positions. Well, unless you get lost trying to find the right section header 😕 There is also a major problem with company presentation. I don&apos;t know about you, but a big list of jobs alone doesn&apos;t get me excited about the company I&apos;m applying to!&lt;/p&gt;&lt;p&gt;So, my team was tasked with ripping out our infinite list of open positions and putting something much friendlier in its place. We also needed to communicate the company&apos;s story better to get people to &lt;strong&gt;smash&lt;/strong&gt; that apply button!&lt;/p&gt;&lt;h2&gt;Lesson 1: Competitive analysis is important!&lt;/h2&gt;&lt;p&gt;Like any starry-eyed CS student, I expect to start hacking on the frontend right away while the design mockups poured in. But slow down there! There was a lot of research we needed to do first. Otherwise, we might end up with another bottomless list of poor usability.&lt;/p&gt;&lt;p&gt;At Peloton, analysis of existing solutions (other career sites in this case) is done by the UX designers. Though I didn&apos;t play a part in the end experience from a UI perspective, I needed to weight in on options for implementation. In other words, how can we load all of our job listings as fast as possible without too much technical overhead?&lt;/p&gt;&lt;p&gt;For some background, Peloton&apos;s existing careers page uses the Greenhouse API to retrieve job postings and display by department header. This is done through a single, extra-large API call to get &lt;strong&gt;all&lt;/strong&gt; the job listings with &lt;strong&gt;all&lt;/strong&gt; of the departments and &lt;strong&gt;all&lt;/strong&gt; of the job descriptions attached. With hundreds of listings and thorough job descriptions on each, that call gets... chunky.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/q323fxatw8g667wz41cz.png&quot; alt=&quot;Screenshot of inspector when loading job postings on a slow 3G connection&quot; /&gt;&lt;/p&gt;&lt;p&gt;If we try to get that on a poor cell connection, it can take up to 10 seconds before we can start scrolling!&lt;/p&gt;&lt;h3&gt;What are some better options?&lt;/h3&gt;&lt;p&gt;With this in mind, I started checking the network activity for existing career pages using Greenhouse. From dev tools alone, I could already piece together some smarter approaches to try:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Paginated loading using a custom endpoint. Since the Greenhouse API doesn&apos;t offer pagination, I&apos;d need to add an endpoint to Peloton&apos;s backend to divvy up the Greenhouse response into pages. This would turn our page into a full-stack endeavor, though it wouldn&apos;t be too difficult.&lt;/li&gt;&lt;li&gt;Using PHP with &lt;a href=&quot;https://github.com/grnhse/greenhouse-tools-php&quot;&gt;Greenhouse&apos;s API plugin.&lt;/a&gt; Though this adds some helper methods for each endpoint, it adds little functionality over just making the calls myself. Plus, I&apos;d have to learn PHP 😬&lt;/li&gt;&lt;li&gt;Building a 100% static site. In other words, no network calls at runtime for &lt;strong&gt;instant&lt;/strong&gt; loading. However, popping a static site generator like &lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;Gatsby&lt;/a&gt; into the repo was almost out of the question due to technical overhead. So, I&apos;d need a custom solution to poll Greenhouse at build-time and keep that list of jobs up to date, which is also fairly complex.&lt;/li&gt;&lt;li&gt;A hybrid of pagination and static content. &lt;a href=&quot;https://www.spotifyjobs.com/search-jobs/&quot;&gt;Spotify Jobs&lt;/a&gt; uses this to show the first page of listings instantly and poll the API for additional content. Though slick, this has similar overhead as building a 100% static page.&lt;/li&gt;&lt;li&gt;Loading just the departments on the homepage for a much smaller API call. When choosing a department, I could retrieve the listings for that department alone.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I noticed another easy win looking closer at the API: each job listing includes a URL linking to the job description and application form. This means we can avoid loading the job descriptions entirely and link to the applications directly, saving a &lt;strong&gt;ton&lt;/strong&gt; on response size.&lt;/p&gt;&lt;h3&gt;How did this inform the design?&lt;/h3&gt;&lt;p&gt;After researching the Greenhouse API and weighing out these options, I had enough info to come back to product and design. With design observing the UX of existing solutions and myself observing the tech behind them, we converged on a solid approach:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Just display a list of departments on the homepage, along with the number of openings for each. This cuts down on API response size while making the homepage more inviting.&lt;/li&gt;&lt;li&gt;Create landing pages for each department. Each should include their respective job listings and a location filter.&lt;/li&gt;&lt;li&gt;Add some extra sections on company mission for some added 🌶️. For the MVP, this meant using the existing company perks section and adding a video on company culture.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This obviously isn&apos;t the perfect solution in the end. What if a user wants to browse all job listings by location? How will someone interested in a retail role browse compared to an engineer scoping out the HQ?&lt;/p&gt;&lt;p&gt;These questions are worth asking as the team fleshes out the best experience going forward. However, we couldn&apos;t lose scope of the problem at hand: making the existing experience snappier and more inviting. If our MVP succeeds in that, it&apos;s still a great starting point!&lt;/p&gt;&lt;h2&gt;Lesson 2: Epics are super helpful for task breakdown&lt;/h2&gt;&lt;p&gt;After researching ideas and deciding on a solution, we needed to start building actionable tasks. I&apos;ve done my fair share of semi-thought-out GitHub issues, but this added a whole world of product terminology to the fold.&lt;/p&gt;&lt;p&gt;The first was the &quot;KPI,&quot; or a key performance indicator. To put it less fancily, we needed to measure the value a feature would have for the project and the company as a whole. This guided the MVP design process to see what the minimum set of features would be to make a kicka** careers page. By extension, this guided the user stories our tasks would focus on.&lt;/p&gt;&lt;p&gt;...Which brings me to user stories. Though this was covered in my college curriculum, I had little perspective on how useful they were in project planning. For those unfamiliar, the format goes &lt;a href=&quot;https://www.youtube.com/watch?v=TLGWQfK-6DY&quot;&gt;a little something like this&lt;/a&gt;:&lt;/p&gt;&lt;p&gt;As a [stakeholder], I want to do [something] so that [reasons].&lt;/p&gt;&lt;p&gt;Based on this, we can figure out all the tasks necessary to make that goal happen. This often leads to an &quot;epic&quot; each actionable task branches off of. I&apos;d think of the epic as an overarching feature we&apos;re developing to address a specific user story. From this, we can work out the major tasks the developers and designers should work on.&lt;/p&gt;&lt;p&gt;For us, this started with a few user stories:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;As a visitor, I want to find the career I&apos;m looking for easily so I can apply.&lt;/li&gt;&lt;li&gt;As a visitor, I want to view all the jobs available for my preferred location so I can view my opportunities there.&lt;/li&gt;&lt;li&gt;As a visitor, I want to learn more about the company so I can decide if it&apos;s a good fit for me.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;From these, we worked out some actionable epics:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Display job listings by department&lt;/li&gt;&lt;li&gt;Allow filtering of job listings&lt;/li&gt;&lt;li&gt;Showcase a video about Peloton company mission&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;With these MVP goals mapped out and mockups in place, it was time to start developing!&lt;/p&gt;&lt;h2&gt;Lesson 3: Smart subtasks lead to manageable PRs&lt;/h2&gt;&lt;p&gt;This was a hard lesson to learn as the king of adding unrelated fixes to my branches 🙃 Though I improved working on team projects in college, I rarely had to plan out a month of tasks myself so PRs could smartly build off of each other. This was difficult at times without getting my hands dirty first, since I may not know the technical challenges of features before starting on them. Even so, instead of diving in head first per usual, I had to sit down and plan a semi-realistic roadmap.&lt;/p&gt;&lt;p&gt;In the end, I worked with the project manager to get a list of steps I&apos;d complete:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Get the careers page displaying at the new URL (we were using &lt;code&gt;[onepeloton.com/careers](http://onepeloton.com/careers)&lt;/code&gt; instead of the old &lt;code&gt;/company/careers&lt;/code&gt; for better discoverability)&lt;/li&gt;&lt;li&gt;Port over the existing &quot;Perks&quot; section to the new page&lt;/li&gt;&lt;li&gt;Get the list of departments pulled in from greenhouse, properly formatted for frontend use&lt;/li&gt;&lt;li&gt;Display the list of departments on the page&lt;/li&gt;&lt;li&gt;Add a banner image with a &quot;call to action&quot; that will scroll to the list of departments&lt;/li&gt;&lt;li&gt;Add independent department pages that can be linked to by name (ex. the &quot;Apparel&quot; department can be reached at &lt;code&gt;onepeloton.com/careers/apparel&lt;/code&gt;)&lt;/li&gt;&lt;li&gt;Pull in job listings from Greenhouse to display on each department page&lt;/li&gt;&lt;li&gt;Add a location filter&lt;/li&gt;&lt;li&gt;Add a department filter that redirects to the different department pages&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Making this holistic overview was really help in judging the scale of each feature. It also helped me figure out which tasks were dependent on others, like setting up URL routing before building out the visuals.&lt;/p&gt;&lt;h3&gt;So how did you make sure each PR was &quot;manageable?&quot;&lt;/h3&gt;&lt;p&gt;Though this task list guided our issue board, some issues were easily large enough for multiple PRs.&lt;/p&gt;&lt;p&gt;Filing down the task list into PR-able subtasks led to some hiccups along the way. For example, I noticed both the department page and landing page needed access to our API dispatch functions, so I restructured these layouts to use a shared wrapper component. An extra PR needed to pop up for these situations to keep the purpose of each push hyper-focused. Though this meant more PRs overall that the team needed to review, the scope of them was much clearer! I learned this pointer from a fellow dev who made sure PRs touched about six files or less, excluding one-line edits for imports and the like.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://dev.to/kylegalbraith/how-to-get-started-with-test-driven-development-today-401j&quot;&gt;Test-driven development&lt;/a&gt; also helped gauge the size of each PR. This was a lesson best learned the hard way... by putting out an &lt;a href=&quot;https://www.dailydot.com/unclick/absolute-unit-meme/&quot;&gt;absolute unit&lt;/a&gt; of a PR no one had time to review.&lt;/p&gt;&lt;p&gt;This happened when taking on a task and judging its scale at a surface level. Here, I was  setting up &lt;a href=&quot;https://prettylinks.com/2018/03/url-slugs/&quot;&gt;URL slugs&lt;/a&gt; for each department, so each could have their own link-able landing pages. At first, this seemed like a simple mapping from the names of each department to a slug and setting up routing to display the right page. This had some minor caveats, like waiting for the departments to come in from Greenhouse before generating the slugs, but this easily built on the code I already had. So, I made a new git branch, started hacking it out, got the pages working...&lt;/p&gt;&lt;p&gt;and realized I wasn&apos;t handling redirects for bad slugs.&lt;/p&gt;&lt;p&gt;This check for redirects ended up being a little more than met the eye. A coworker showed me a much simpler way to handle redirects using &lt;a href=&quot;https://redux.js.org/&quot;&gt;Redux state management&lt;/a&gt;, but it took over a week for him to get around to reviewing my code!&lt;/p&gt;&lt;p&gt;This is a classic example of how thinking in terms of tests could have shown me all cases I needed to consider, making it easier to break everything down. In my experience, the size of the test file often reflects the size of the final PR. So, if those unit tests are easy to predict, try writing them out early to understand the scope of a feature. If getting 100% test coverage isn&apos;t possible, try writing down all the possible use cases for the feature before diving in 😁&lt;/p&gt;&lt;h2&gt;Lesson 4: Designer communication is key&lt;/h2&gt;&lt;p&gt;I will confess, this wasn&apos;t my first time working with designers on a project. However, they were usually less experienced or working on smaller scale projects with a lot of flexibility around the end product&apos;s design. Working with trained UX... xperts on a new project was a new experience, but an exciting one!&lt;/p&gt;&lt;p&gt;First off, getting regular feedback from designers was very helpful for hacking out the CSS. This meant UAT reviews, or giving feedback on a test version of the site, and desk-side checks for more visual PRs. At Peloton, designers work on a pretty strict desktop - tablet - mobile mockup system with specific pixel breakpoints for each. This led to some detailed feedback on all of the layouts I created, down to the smallest 10px padding adjustment.&lt;/p&gt;&lt;p&gt;As you may expect from such detailed guidelines, Peloton has a comprehensive design system to manage the scalability of elements. In fact, they have a small team of &quot;UI engineers&quot; communicating closely with designers to keep their system up to date. This means using &lt;a href=&quot;https://storybook.js.org/&quot;&gt;Storybook&lt;/a&gt; to keep track of all the buttons, headings, banners, etc. for both designers and developers to reference. This made my job a lot easier working out sizing and spacing for text and navigational components, with just a handful of custom layouts left to style by hand. Just paying attention to the breakpoints and using flexbox was all it took to handle these effectively!&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/6w3ucpry1fwyz3lm924r.gif&quot; alt=&quot;Resizing browser window on new careers landing page&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;The final landing page for careers. All buttons, headers, and image sizing are from Storybook components, while the arrow icons and 2-column layout use custom CSS.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;There was also close communication on imagery used for each page of the MVP. Namely, we needed to work out breakpoints for where the image would need to be cropped. For example, if a figure at the right side of the image on desktop needed to appear in the center on mobile, two differently cropped versions of that image would be necessary. I could use some positioning magic to crop using pure CSS, but since our project used &lt;a href=&quot;https://cloudinary.com/&quot;&gt;Cloudinary&lt;/a&gt; to serve differently sized images based on screen width, there was no reason to get that hacky!&lt;/p&gt;&lt;h2&gt;Wrapping up&lt;/h2&gt;&lt;p&gt;My time at Peloton this summer was an amazing and rewarding experience. It&apos;s rare that a lowly intern can be the main developer on a project team, given the same responsibilities and expectations as a regular employee. It&apos;s even rarer that an intern gets to present that feature to company stakeholders, and have that project get deployed to production unchanged! If you&apos;re interested, you can &lt;a href=&quot;https://careers-page-demo.surge.sh/&quot;&gt;view my slide deck&lt;/a&gt; from the MVP presentation (yes, it&apos;s using &lt;a href=&quot;https://github.com/jxnblk/mdx-deck&quot;&gt;mdx-deck&lt;/a&gt;!).&lt;/p&gt;&lt;p&gt;And to check out the finished product, &lt;a href=&quot;https://www.onepeloton.com/careers&quot;&gt;go here&lt;/a&gt;!&lt;/p&gt;&lt;p&gt;Overall, I am extremely grateful for my time at the company, and I&apos;m excited to see how the careers project progresses. I hope these lessons I learned along the way help you on your MVP development journey! 🚀&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Using Typescript string enums? Consider string literals!</title><link>https://bholmes.dev/blog/using-enums-with-string-values-in-typescript-consider-string-literals-instead/</link><guid isPermaLink="true">https://bholmes.dev/blog/using-enums-with-string-values-in-typescript-consider-string-literals-instead/</guid><description>Enums are a great way to standardize your types. But if you want those string values, is this the best solution?</description><pubDate>Mon, 29 Jul 2019 14:39:03 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;If you&apos;ve been using TypeScript for any amount of time, you&apos;ve probably wondered this at least once:&lt;/p&gt;&lt;p&gt;&lt;em&gt;Can I use string values instead of numbers on TypeScript enums?&lt;/em&gt;&lt;/p&gt;&lt;p&gt;This often comes up when you want a variable to have a select few string values. For example, say you&apos;re creating a banner for a website that&apos;s either yellow for warnings or red for emergencies. You want to make something reusable, so you add a enum for which type of banner it is:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;enum&lt;/span&gt;&lt;span&gt; BannerType&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    Warning&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;warning&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    Danger&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;danger&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This gives you a lot of flexibility of how you can use that enum&apos;s value. A common use might be defining a class name for styling your banner:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;/* Yes, this is written a JSX-y fashion for you React users */&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{BannerType.Danger}&amp;gt;Uh oh!&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is much easier than writing weird helper functions and ternaries to figure out what class name to use. There are many more use cases for enum string values, like object keys, CMS content identifiers, paragraph text, error logs, etc etc etc.&lt;/p&gt;&lt;h2&gt;When string enums fall flat&lt;/h2&gt;&lt;p&gt;There&apos;s a few annoyances you might find with enums + string initializers:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;They&apos;re a little verbose&lt;/li&gt;&lt;li&gt;They require lookups and tooltips to see what the actual string value is&lt;/li&gt;&lt;li&gt;They&apos;re limited in the special characters the enum can use&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This last point was a huge point of friction for my web development team. To explain, we were looking to generate keys for content coming from the &lt;a href=&quot;https://www.contentful.com/&quot;&gt;Contentful CMS&lt;/a&gt;. In Contentful, a key can be any string you could dream up. This means you can, say, include dots to indicate a subcategory (ex. &quot;labels.danger&quot;) or dashes to mirror &lt;a href=&quot;https://prettylinks.com/2018/03/url-slugs/&quot;&gt;URL slugs&lt;/a&gt; (ex. &quot;checkout-promo-code&quot;).&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Clarification&lt;/strong&gt;: A &quot;CMS&quot; is an external service to host all of the content for your website. In our case, we are using Contentful to store all of the header text, body text, images, and videos we display. In order to retrieve this content, we make an API call to fetch by specific keys.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;This poses a problem for our enum solution. We need to use the keys in order to retrieve the site&apos;s content, and mapping each Contentful key to an enum means tossing out all the dots and dashes! Needless to say, this could lead to some nasty collisions between keys that are unique in Contentful but not unique on our hacky enums.&lt;/p&gt;&lt;h2&gt;String literals to the rescue!&lt;/h2&gt;&lt;p&gt;Luckily, TypeScript has a cleaner solution when you need those string values. You can provide a finite list of strings a variable can be assigned. Otherwise, it should throw a type error.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/vg17yj980e8yzi3s3ihj.gif&quot; alt=&quot;Example of assigning something invalid to a string literal type&quot; /&gt;&lt;/p&gt;&lt;p&gt;This will also prevent you from assigning a traditional &quot;string&quot; type to the string literal. So, when declaring your types, you&apos;ll need to export the string literal type and use it the same way you would use an enum.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/4l8srodbtpwfn88dp22d.gif&quot; alt=&quot;Example of autocomplete for string literals using VS Code&quot; /&gt;&lt;/p&gt;&lt;p&gt;You can see from the informative gif above that autocomplete works as well!&lt;/p&gt;&lt;h3&gt;Limitations&lt;/h3&gt;&lt;p&gt;String literals aren&apos;t the silver bullet for every situation. Notably, using string literals doesn&apos;t improve on the verbose nature of enums. In fact, it&apos;ll often provide more information than necessary when assigning the literal type.&lt;/p&gt;&lt;p&gt;It&apos;s also more visually unclear what all possible values are when assigning &lt;code&gt;&apos;random string&apos;&lt;/code&gt; instead of &lt;code&gt;SpecificTypes.Enum&lt;/code&gt;. This requires team communication to decide if string literals work best for smooth PR reviewing and text editor / IDE support.&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>How to revert your git commits without panicking</title><link>https://bholmes.dev/blog/how-to-revert-your-git-commits-without-panicking/</link><guid isPermaLink="true">https://bholmes.dev/blog/how-to-revert-your-git-commits-without-panicking/</guid><description>When your code turns to 💩 but you&apos;ve already committed, how do you go back?</description><pubDate>Thu, 13 Jun 2019 02:23:26 GMT</pubDate><content:encoded>&lt;article&gt;&lt;h2&gt;Step 1: Take deep breaths&lt;/h2&gt;&lt;p&gt;The best way to overcome your escalating fear that your codebase / world is collapsing around you is deep breathing. Before frantically flying to the keyboard, just go &lt;em&gt;in through the nose... and out through the mouth.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/icmotiqzlpbpgucj0rhi.jpg&quot; alt=&quot;Person meditating in front of an orange sunset&quot; /&gt;&lt;/p&gt;&lt;h2&gt;Step 2: Look over the commit log&lt;/h2&gt;&lt;p&gt;Before doing anything you might regret, it&apos;s best to look through all recent commits to pinpoint where you&apos;re reverting to. The cleanest way to do so is using:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; log&lt;/span&gt;&lt;span&gt; --oneline&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/sgohk5klbw8z2nxfjuey.png&quot; alt=&quot;git log of commit history&quot; /&gt;&lt;/p&gt;&lt;p&gt;This will show all of the commit history for your branch, starting with the most recent. It also shows branch points to see the history of everything that got merged.&lt;/p&gt;&lt;p&gt;Also notice the alphanumeric strings to the left of each commit. These are hashes which uniquely identify a given commit. Once you find the inflection point for your code explosion, copy the hash for that commit and move on to the next step.&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note&lt;/strong&gt;: You can exit this log by hitting the &quot;q&quot; key. This is very &lt;a href=&quot;https://vim.dev/&quot;&gt;vim&lt;/a&gt; so I don&apos;t blame you if you got stuck here&lt;/em&gt; 😛&lt;/p&gt;&lt;h2&gt;Step 3: Diff against the most recent commit&lt;/h2&gt;&lt;p&gt;Checking for differences against your revert point is a great way to verify what changes you&apos;re about to make. To do so, use the copied hash and run the following command:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; diff&lt;/span&gt;&lt;span&gt; HEAD&lt;/span&gt;&lt;span&gt; COPIED_HASH^&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/12cvckmewfkubzntuhky.png&quot; alt=&quot;git diff sample output&quot; /&gt;&lt;/p&gt;&lt;p&gt;Let&apos;s break this output down. First, we see a log of all binary files changed, with paths listed as &lt;code&gt;a/file_path&lt;/code&gt; and &lt;code&gt;b/file_path&lt;/code&gt;. These files are often editor / build specific files (as in &lt;code&gt;.vscode&lt;/code&gt; or &lt;code&gt;.circleci&lt;/code&gt; directories) and image files. In this case, the the resolution of a few images were updated.&lt;/p&gt;&lt;p&gt;Next, we see a line-by-line breakdown of lines added and removed from each modified file. Note this is from the perspective of changing &lt;em&gt;from&lt;/em&gt; our most recent commit &lt;em&gt;to&lt;/em&gt; the commit we&apos;re reverting to. So, everything in green is what will be added &lt;em&gt;after&lt;/em&gt; reverting, and everything in red will be removed.&lt;/p&gt;&lt;p&gt;Also note the &lt;code&gt;^&lt;/code&gt; at the end there. This makes sure we diff down to the ancestor of that commit hash. Otherwise, the diff will stop one commit early.&lt;/p&gt;&lt;p&gt;If you scroll through this and everything looks good, it&apos;s time to obliterate those commits!&lt;/p&gt;&lt;h2&gt;Step 4 Option 1: Run a commit revert (recommended)&lt;/h2&gt;&lt;p&gt;The first option is the most obvious one: run the &lt;code&gt;revert&lt;/code&gt; command onto the commit hash we&apos;re reverting to. This is very straightforward:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; revert&lt;/span&gt;&lt;span&gt; COPIED_COMMIT_HASH..&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that doing so will create a new commit meant to revert all changes made down to the commit specified. This works well since you should be able to push your changes and have a clean change log as intended.&lt;/p&gt;&lt;p&gt;Worth warning that this will open a confirmation file in vi (assuming you&apos;re using the default git editor) for &lt;strong&gt;each&lt;/strong&gt; commit being reverted. If everything looks as expected, type &quot;:wq&quot; to confirm the reverts.&lt;/p&gt;&lt;p&gt;You can also add the &lt;code&gt;--no-commit&lt;/code&gt; flag to stage each revert without actually creating a revert commit. This is useful when reverting several commits deep since it allows you to submit one large revert commit at the end. Otherwise, you would end up submitting a revert commit for each one individually.&lt;/p&gt;&lt;p&gt;Still, you might want to change your commit history entirely so it appears those nasty commits were never there. For that, there is a second option...&lt;/p&gt;&lt;h2&gt;Option 2: Rebase and drop commits&lt;/h2&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note&lt;/strong&gt;: Read the disclaimer at the end of this section before going with this option.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Rather than making a revert commit, you can modify the commit history directly using a rebase. To start the process, type the following:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; rebase&lt;/span&gt;&lt;span&gt; -i&lt;/span&gt;&lt;span&gt; COPIED_COMMIT_HASH^&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/p8zgqcct0ncexpwzv2e0.png&quot; alt=&quot;git interactive rebase sample output&quot; /&gt;&lt;/p&gt;&lt;p&gt;Notice the &lt;code&gt;-i&lt;/code&gt; flag here. This is a shorthand for &lt;code&gt;--interactive&lt;/code&gt;, which allows you to edit each commit individually from the head to our specified commit hash.&lt;/p&gt;&lt;p&gt;It&apos;s simple from here to drop all of our commits. The instructions are actually visible right there in the comments, but as a TLDR: Replace every &quot;pick&quot; with a &quot;drop&quot; for all commits listed.&lt;/p&gt;&lt;p&gt;...If you&apos;re stuck wondering why your delete key isn&apos;t working, that&apos;s because git uses &lt;a href=&quot;https://vim.dev/&quot;&gt;vi&lt;/a&gt; as the default editor. You can go &lt;a href=&quot;https://www.kevinkuszyk.com/2016/03/08/git-tips-2-change-editor-for-interactive-git-rebase/&quot;&gt;here&lt;/a&gt; to change over to your favorite editor for this process, but if you&apos;re fine sticking with vi, it shouldn&apos;t be too difficult.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;To start editing, hit the &quot;i&quot; key and use the arrow keys to move your cursor around&lt;/li&gt;&lt;li&gt;Move your cursor over to each &quot;pick&quot; statement, delete, and replace it with &quot;drop&quot;&lt;/li&gt;&lt;li&gt;Once all the picks are replaced, hit &quot;esc&quot;&lt;/li&gt;&lt;li&gt;To confirm the rebase, type &quot;:wq&quot;. This will save the rebase file you just edited&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;If you want to abort at any time, delete all the lines listed above the commented section (those starting with &quot;#&quot;) to force the rebase to error out. If you&apos;re using vi, just mash &quot;d&quot; repeatedly until the lines are gone 🙃&lt;/p&gt;&lt;p&gt;If your terminal window closes or something else happens, the rebase will hang uncompleted. To abort the rebase and start over, enter the command &lt;code&gt;git rebase --abort&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;If all goes well, you will have officially changed history!&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://media.giphy.com/media/M6VxE9CEHMDtK/giphy.gif&quot; alt=&quot;Applicable Doctor Who quote: wibbly wobbly timey wimey stuff&quot; /&gt;&lt;/p&gt;&lt;h3&gt;Disclaimer&lt;/h3&gt;&lt;p&gt;Modifying git history can be a problem if you have already pushed now reverted commits to the remote branch. After reverting, it will appear your branch is behind the remote, so you cannot push directly anymore. In this case, you will have to run &lt;code&gt;git push --force-with-lease&lt;/code&gt;. This flag will force your history onto the remote by removing those dropped commits. &lt;code&gt;with-lease&lt;/code&gt; acts as an error check to ensure the rest of your commits are in line with the remote&apos;s.&lt;/p&gt;&lt;p&gt;You may have heard using &lt;code&gt;--force&lt;/code&gt; on a push isn&apos;t best practice since it involves rewriting commit history on the remote. This can be a problem for others working off of the same branch, since their history will no longer line up with the remote&apos;s. &lt;a href=&quot;https://stackoverflow.com/questions/9813816/git-pull-after-forced-update&quot;&gt;There are ways to fix this&lt;/a&gt;, but make sure others are aware of your force push before they run into trouble!&lt;/p&gt;&lt;h2&gt;Wrapping up&lt;/h2&gt;&lt;p&gt;Hopefully by the end of this you&apos;ve gotten your branch back on track! Mainly, always make super sure of what commands you&apos;re about to run when modifying commits, especially when editing git history directly. This can be a very daunting task for beginners and experts alike, so making sure of the diff and viewing the commit log are a must!&lt;/p&gt;&lt;p&gt;I actually wrote this article after having to go through the same process for my job, wanting to give my hastily written notes some clarity. Hope this helps others stuck in the same boat 😊&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>How using CSS variables helped me cut down on JavaScript</title><link>https://bholmes.dev/blog/how-using-css-variables-cut-down-on-my-javascript/</link><guid isPermaLink="true">https://bholmes.dev/blog/how-using-css-variables-cut-down-on-my-javascript/</guid><description>Styling with JavaScript is pretty convenient, but how can CSS replicate that logic?</description><pubDate>Fri, 31 May 2019 03:37:13 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; My post is inspired by &lt;a href=&quot;https://css-tricks.com/keep-math-in-the-css/&quot;&gt;this article&lt;/a&gt; from the brilliant Chris Coyier about the power of CSS &lt;code&gt;calc()&lt;/code&gt;. Definitely worth a read!&lt;/em&gt;&lt;/p&gt;&lt;p&gt;If you’ve been keeping up with web development since 2018, you’ve probably come across &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/--*&quot;&gt;CSS custom properties / variables&lt;/a&gt;. They’ve become the new hotness in applications even beyond just using raw CSS, as they offer scoping and cascading that even new CSS-in-JS solutions like &lt;a href=&quot;https://www.styled-components.com/docs/basics&quot;&gt;Styled Components&lt;/a&gt; don’t directly replicate.&lt;/p&gt;&lt;p&gt;I hadn’t given CSS custom properties much of a fair shake when I first learned about them since I’ve become such a prop slinging, CSS-in-JS devotee (please hold your criticism 😛), but my recent project required me to go back to traditional stylesheets due to using the exotic framework, &lt;a href=&quot;https://svelte.dev/&quot;&gt;SvelteJS&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;At first, simply declaring styles as necessary without any form of variables seemed manageable; whenever something custom needed to happen based on code, I could just whip up a string of inline CSS and tack it onto my element without worrying too much about optimization. However, while the website still seemed snappy, my codebase was growing less and less readable. Enter: CSS variables!&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Sidenote:&lt;/strong&gt; There was a time people would shout from the rooftops &quot;they&apos;re not called &lt;strong&gt;variables&lt;/strong&gt;; they&apos;re &lt;strong&gt;custom properties!&lt;/strong&gt;&quot; Thankfully, relevant MDN documentation and general lingo has caught up to just calling them variables. So either works, but &quot;variables&quot; is a little clearer in my opinion&lt;/em&gt; 😊&lt;/p&gt;&lt;h2&gt;So how do CSS variables work?&lt;/h2&gt;&lt;p&gt;To those unfamiliar, you can declare a variables within any element you choose, or within the &lt;code&gt;:root&lt;/code&gt; selector to make it globally accessible. Just use &lt;code&gt;--[property-name]&lt;/code&gt; syntax so CSS will pick it up as a variable…&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;:root&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    --global-var&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;.some-class&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    --scoped-var&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;… and then use those variables in sub-elements (or any element for global properties) using &lt;code&gt;var(--[property-name])&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.child-of&lt;/span&gt;&lt;span&gt; .some-class&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    margin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--scoped-var&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is similar to how &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors&quot;&gt;CSS attributes&lt;/a&gt; work, except CSS variables can take on any unit of measure you choose, then be used to define any CSS property. This means you get the same kind of flexibility that variables in pre-processors have been offering for years now, though with an admittedly clunkier syntax (hey, that’s CSS3 for you 🤷‍♀).&lt;/p&gt;&lt;p&gt;What’s not as well known is how CSS variables can be &lt;strong&gt;unitless.&lt;/strong&gt; This doesn’t seem like a huge deal at first, but it offers a big advantage: combined with &lt;code&gt;calc()&lt;/code&gt;, CSS variables can be used to scale properties by a set amount. This was invaluable in refactoring own code, since it meant I could rewrite my CSS string construction in JavaScript with just a couple lines of CSS calculation.&lt;/p&gt;&lt;h2&gt;Let’s see an example&lt;/h2&gt;&lt;p&gt;To show custom properties in action, I’ll take a logic snippet from a portfolio concept I built out.&lt;/p&gt;&lt;p&gt;The goal was simple: I wanted an accent bar to cycle through a set of gradients on a loop, shifting from one gradient to the next. This would be possible with a single animation keyframe, though I had a caveat: a looping interval was being used on other elements of the page with logic CSS couldn’t replicate, and I wanted to use the same interval in my accent bar for consistency&apos;s sake. This interval was, of course, defined in JavaScript using &lt;code&gt;setInterval(...)&lt;/code&gt;. Whenever the callback function was hit, some CSS needed to change. This interval was set in a parent component and accessed within my accent bar component (yes, I&apos;m using a component-based framework).&lt;/p&gt;&lt;p&gt;Before diving into the example, note that this project was built on Svelte. This shouldn&apos;t affect the readability of the code too badly; just accept that the lesser details involve some magic ✨&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/dlxut0dvc69sx06dzuzp.gif&quot; alt=&quot;Animated gradient of colors&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;The end goal&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;My old way of doing it&lt;/h2&gt;&lt;p&gt;Originally, I cycled through the gradient by creating a wide background image with hidden overflow, then shifting the background position on each tick of the interval. This gave the illusion of the background shifting colors, though it was essentially moving through one large gradient. However, this background position required a fair amount of calculation.&lt;/p&gt;&lt;p&gt;To keep all the interval tracking simple across several components, I was keeping track of a  &lt;code&gt;gradientIndex&lt;/code&gt; variable passed as a prop. This index corresponds to a list of gradient colors I am cycling through called &lt;code&gt;GRADIENTS&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;However, this means some extra logic is necessary to update the CSS: whenever the &lt;code&gt;gradientIndex&lt;/code&gt; is changed, a new CSS string needed to be constructed to be applied as an inline style. Thus, we need to dig out a lifecycle method to construct our strings when the &lt;code&gt;gradientIndex&lt;/code&gt; prop changes. In Svelte, this is accomplished using the &lt;code&gt;afterUpdate&lt;/code&gt; callback function:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;afterUpdate&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  backgroundPosition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; `${&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;GRADIENTS&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; gradientIndex&lt;/span&gt;&lt;span&gt;}%`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We also need to figure out the background size for our overflow by getting a percentage from &lt;code&gt;GRADIENTS.length&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; backgroundSize&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `${&lt;/span&gt;&lt;span&gt;GRADIENTS&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; 200&lt;/span&gt;&lt;span&gt;}% 100%`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, we throw this into our inline style alongside our constructed linear gradient background:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;bar&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;background-image: {backgroundImage};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  background-position: {backgroundPosition};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  background-size: {backgroundSize}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So yes, the end result functions pretty well without any performance problems... on my overpowered MacBook anyways 😛 However, we&apos;ve added a fair amount of complexity that will only get worse as we scale. We&apos;ve added a lifecycle method to handle our inline CSS construction, and we&apos;re littering our JavaScript with variables that would ideally be kept within the styles where they belong. If only there was a way to calculate this using just CSS!&lt;/p&gt;&lt;h2&gt;A new, more readable solution&lt;/h2&gt;&lt;p&gt;So how can we tackle this problem using CSS variables? Well, looking at the string for background position constructed in JS, we see the calculation requires knowing how many gradients there are (&lt;code&gt;GRADIENTS.length&lt;/code&gt;) and the current index to figure out position (&lt;code&gt;gradientIndex&lt;/code&gt;). So, why not just make each of these CSS variables?&lt;/p&gt;&lt;p&gt;Thankfully, CSS variables are settable using inline styles like any other CSS property (the same can’t be said for variables in SASS!). So, let’s say both our aforementioned variables are part of the component state. We can make them visible to CSS using the following inline style:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;bar&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;background-image: {backgroundImage};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --index: {gradientIndex};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --length: {gradientLength}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we can determine our background size and position within CSS just by using &lt;code&gt;calc()&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.bar&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --index&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --length&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  background-size&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;calc&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--length&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 200&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  background-position&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;calc&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--length&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;)) * &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  --index&lt;/span&gt;&lt;span&gt;) * 1%);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There are a couple things to unpack here. First, we set each variable to some initial value for the sake of completeness. This isn’t necessary since the inline style should always be applied, though initializing CSS variables is a good habit to get into. Next, we set our background position similar to our JS, with one notable difference: we multiply the &lt;code&gt;--index&lt;/code&gt; by a percentage, rather than write the percent sign directly after the variable. This is because &lt;code&gt;calc()&lt;/code&gt; treats the variable like a constant in mathematics, so it has to be multiplied by some value for a unit of measure to be applied.&lt;/p&gt;&lt;p&gt;Oh, and here’s our new JS snippet: …wait, there isn’t one anymore! 🎉&lt;/p&gt;&lt;h3&gt;Can we go even deeper?&lt;/h3&gt;&lt;p&gt;Something this example doesn&apos;t take advantage of is variable cascading. This is super useful for component-based development, since you can consolidate a lot of wacky CSS calculation into the parent component. Then, child components can just access the CSS variables from higher up in the cascade. In our example, we could make &lt;code&gt;gradientIndex&lt;/code&gt; a CSS variable in the parent wrapping around our color bar and avoid passing it as a prop entirely!&lt;/p&gt;&lt;p&gt;Of course, this can start to have a negative impact on readability, with variables several levels up cascading down without the developer realizing it. This exposes the age-old conflict between cascade thinking and component-based thinking, so use this technique sparingly.&lt;/p&gt;&lt;h2&gt;Wrapping up&lt;/h2&gt;&lt;p&gt;With that, it should be clear that custom properties can be pretty powerful to take your JavaScript logic over to the stylesheet. Additionally, now that CSS variables are compatible with most all modern browsers (except IE of course 😢), they should be pretty safe to experiment with even in production code. So go forth and get to styling!&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Git + GitHub Best Practices for Teams (Opinionated)</title><link>https://bholmes.dev/blog/git-github-best-practices-for-teams-opinionated/</link><guid isPermaLink="true">https://bholmes.dev/blog/git-github-best-practices-for-teams-opinionated/</guid><description>A collection of thoughts on using Git to its fullest when working on a project team</description><pubDate>Thu, 16 May 2019 19:50:13 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; This was adapted from a guide written internally for our Bits of Good organization to help newbies get their footing when joining a project team. You can learn more about the organization &lt;a href=&quot;https://bitsofgood.org/&quot;&gt;here&lt;/a&gt; if you&apos;re interested. Yes, we are reworking the website these next few months :)&lt;/em&gt;&lt;/p&gt;&lt;p&gt;There are &lt;a href=&quot;https://datree.io/github-best-practices/&quot;&gt;P&lt;/a&gt; &lt;a href=&quot;https://yeti.co/blog/get-the-most-out-of-git-7-best-practices-for-budding-developers/&quot;&gt;L&lt;/a&gt; &lt;a href=&quot;https://www.git-tower.com/learn/git/ebook/en/command-line/appendix/best-practices&quot;&gt;E&lt;/a&gt; &lt;a href=&quot;https://raygun.com/blog/git-workflow/&quot;&gt;N&lt;/a&gt; &lt;a href=&quot;https://medium.freecodecamp.org/how-git-best-practices-saved-me-hours-of-rework-cf227bad9a50&quot;&gt;T&lt;/a&gt; &lt;a href=&quot;https://git-scm.com/book/en/v2&quot;&gt;Y&lt;/a&gt; of articles out there with a new spin on how to best use Git. Given how versatile and free-form the tool can be, it&apos;s easy to find a new system that works for someone. As such, there really isn&apos;t a catch-all gold standard that everyone &lt;strong&gt;must&lt;/strong&gt; follow. So don&apos;t take the contents of this article as the law! This is merely an opinion piece with suggestions our Bits of Good organization has found to work well.&lt;/p&gt;&lt;p&gt;Note that Bits of Good is a college-level organization, so my opinions are based on experiences with smaller teams rather than working at large companies. So, issues like backlogs, ticketing, and user feedback are not covered here given my limited exposure.&lt;/p&gt;&lt;h2&gt;Creating and Organizing Issues&lt;/h2&gt;&lt;p&gt;Before jumping into modifying the codebase, let&apos;s talk about organizing what needs to be done first. A nifty GitHub feature is the option to create &quot;issues&quot; to break down tasks into assignable chunks. This is helpful in a team setting to decide on deadlines and priorities to keep everyone on track.&lt;/p&gt;&lt;h3&gt;What a good issue should say&lt;/h3&gt;&lt;p&gt;Let&apos;s start with an example of a bad issue:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/tpjshixy7vhwkb2c1nji.png&quot; alt=&quot;Example of bad GitHub issue&quot; /&gt;&lt;/p&gt;&lt;p&gt;Not only is it unclear how the final result should look, but it&apos;s nowhere near enough direction to keep a newbie&apos;s head from exploding. Do everyone a favor and break issues down!&lt;/p&gt;&lt;p&gt;GitHub themselves are nice enough to include example headers whenever you create a new issue. These are super nice for getting started, but may prove to be a little too much for the issues you create. At a minimum, try to include:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;If it&apos;s a feature: A description of the feature being added to the project&lt;/li&gt;&lt;li&gt;If it&apos;s a bug: A description of what the bug is and how to replicate it&lt;/li&gt;&lt;li&gt;A breakdown of &lt;strong&gt;action items&lt;/strong&gt;. This should be a list (with checkboxes preferably) of bite-sized tasks to complete in order to finish the feature / resolve the bug&lt;/li&gt;&lt;li&gt;General notes for the feature / bug or improvements that may be needed down the road&lt;/li&gt;&lt;li&gt;Discussion points if the team still needs to decide on certain details&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The action items should be the bulk of the issue. You can get as granular or technical as you want, so long as it makes someone&apos;s life easier trying to read the issue. Good ideas are to include hyperlinks to potential solutions and supporting images like mockups as a guide.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Optional:&lt;/strong&gt; Break down the action items like a tutorial, walking the reader through one potential method of coding out the issue. This is great for new team members so they don&apos;t feel lost navigating an unfamiliar codebase.&lt;/p&gt;&lt;p&gt;Here&apos;s an example of how to beef up that confusing one-liner issue from earlier:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/whvbzrw1l6cesm5xn76j.png&quot; alt=&quot;Example of good GitHub issue&quot; /&gt;&lt;/p&gt;&lt;h3&gt;Leveraging milestones&lt;/h3&gt;&lt;p&gt;If your team happens to use &lt;a href=&quot;https://linchpinseo.com/the-agile-method/&quot;&gt;Agile development&lt;/a&gt; or another form of rapid release cycles, you should probably avoid chucking issues around without any reference for completion timeframes. GitHub allows you to get a focused view of what you&apos;re working on for each dev cycle / sprint using &quot;milestones.&quot; This is little more than a subset of issues in the repo at a given time.&lt;/p&gt;&lt;p&gt;To view these, look for the &quot;milestones&quot; button within the &quot;Issues&quot; tab. From here, you can create a new milestone with a useful title, description, and deadline. You may tack an issue onto a given milestone using the sidebar when creating a new issue. In general, make sure you don&apos;t have too many issues in a given milestone. It may take some time to feel out how long certain tasks will take, so stay optimistic in the beginning and pull back depending on the workload.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/wc3c731g2nsb9o3cbldh.png&quot; alt=&quot;Example of a GitHub milestones setup&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;A look at our team&apos;s past milestones working on an Agile development schedule. We found emojis can help focus milestone objectives&lt;/em&gt; 😛&lt;/p&gt;&lt;h2&gt;Branch flow using Gitflow&lt;/h2&gt;&lt;p&gt;It&apos;s easy to jump into a new project and immediately think &quot;screw the formalities, I&apos;m gonna jump into some code!&quot; Well, it&apos;s easy to forget an important detail when blinded by excitement: you should always branch off of a development branch first!&lt;/p&gt;&lt;p&gt;This approach is often lost on beginner Git projects, branching off of master alone and merging in whenever code gets the thumbs up. This approach works well on small-scale projects that are only seen by developers most of the time. However, When users get involved that may access the live project at any time, pushing to a &quot;production&quot; branch like master for every change may not always be a good idea. For example, say the you&apos;re on a team building a notification dashboard, with two developers splitting up the UI and a third working on API endpoints. Instead of pushing to master or working off of some awkward middle-way branch, wouldn&apos;t it be nice to push to a develop branch to merge everyone&apos;s changes gracefully?&lt;/p&gt;&lt;p&gt;This process is known as &lt;a href=&quot;https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow&quot;&gt;Gitflow&lt;/a&gt;, using a &lt;code&gt;develop&lt;/code&gt; branch for building out features and a &lt;code&gt;master&lt;/code&gt; branch for the user-facing code. Other flows include further branching stages such as a &lt;code&gt;staging&lt;/code&gt; branch, used for testing and validating code before it is merged into the &lt;code&gt;master&lt;/code&gt; branch.&lt;/p&gt;&lt;p&gt;Setting up this flow is extremely easy in Git. Just leave the default &lt;code&gt;master&lt;/code&gt; branch, treating it as the production branch, and checkout a new &lt;code&gt;develop&lt;/code&gt; branch off of &lt;code&gt;master&lt;/code&gt; to branch from going forward. Also make sure all pull requests made to the project are targeted at this branch instead of master! The only PR to master should be from the develop branch itself most of the time.&lt;/p&gt;&lt;h2&gt;Branching best practices&lt;/h2&gt;&lt;p&gt;If you&apos;ve been through a workshop or basic Git tutorial before, you&apos;ve probably made branch names like &lt;code&gt;Ben-Holmes&lt;/code&gt;, &lt;code&gt;bug-fix&lt;/code&gt;, or &lt;code&gt;homepage-57&lt;/code&gt;. &lt;strong&gt;Please don&apos;t do this!&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;As a general rule, try to title branches like an overarching todo task for yourself. So, it should be descriptive enough that you know exactly what you&apos;re working on, but kept to 5-7 words or less so it&apos;s not impossible for teammates to type. In addition, the scope of a branch should be more focused than broad so all your commits are working on a common issue. This has an added bonus of avoiding little &quot;side quests&quot; while you&apos;re coding. If you see a little spelling mistake on the splash page that has nothing to do with what you&apos;re working on, don&apos;t make the fix in your current branch!&lt;/p&gt;&lt;h3&gt;Formatting&lt;/h3&gt;&lt;p&gt;You can use whatever works best for the team of course, but a solid practice is to:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Start the branch name with either your name or your Git(hub, lab, whatever) username. This makes it easier to come back and find your branches later.&lt;/li&gt;&lt;li&gt;Include the issue number your branch resolves. If your building something without an issue attached (don&apos;t do this too often!), use a short tag on what your branch is trying to resolve. This could be &lt;code&gt;feature&lt;/code&gt;, &lt;code&gt;bug&lt;/code&gt;, &lt;code&gt;poc&lt;/code&gt;, etc.&lt;/li&gt;&lt;li&gt;Add a 4-5 word title on what your branch does. You can use dashes, underscores or camel case to separate the words.&lt;/li&gt;&lt;li&gt;Use a consistent character to separate all of these 👆 like a dot or a dash. You can also uses slashes, but this can have some &lt;a href=&quot;https://github.com/desktop/desktop/issues/7397&quot;&gt;unintended git-y consequences&lt;/a&gt;!&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Let&apos;s see an example&lt;/h3&gt;&lt;p&gt;Say you’re working on an issue titled &quot;&lt;strong&gt;#154&lt;/strong&gt;: Add profile picture editing to the &apos;My Account&apos; page.&quot; As the title implies, you are adding an option to the user&apos;s account management page to edit their profile picture, say, when they click on the image. Here&apos;s a decent branch name for the issue:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git checkout -b pr0H@ck3r.154.my_account_edit_profile_pic&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Yes, this is a pretty rigid structure for a simple branch name. However, it really improves scan-ability when there are tens or even hundreds of branches open at a given time!&lt;/p&gt;&lt;h2&gt;Committing best practices&lt;/h2&gt;&lt;p&gt;It&apos;s the end of the day. You&apos;ve exhausted your third cup of coffee and finally squashed an Internet Explorer bug that took hours to track down. Now you&apos;re ready to push up your changes and finally shut your computer. So, what&apos;s the best way to communicate all the pain and suffering you worked through with the team?&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git commit -m &quot;fixes&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;😓☕️ Well... it&apos;s definitely not this. The commit message isn&apos;t some throw-away blob of text that no one will ever look at. This is the developer&apos;s chance to explain what their code is meant to accomplish, so when a peer combs through the log of changes you made, they will be able to step through the commits and track which changes are worth adding. This is invaluable for the occasional rollback, when changes made beyond a certain commit should get scrapped.&lt;/p&gt;&lt;h3&gt;Writing a good commit message&lt;/h3&gt;&lt;p&gt;So, how can you make the most of a commit message? First, it&apos;s best to start with some verb to describe what adding the commit to the project will accomplish. This is often a present tense phrase like &quot;add,&quot; &quot;fix,&quot; or &quot;include.&quot;&lt;/p&gt;&lt;p&gt;Following this, include a brief phrase to describe the commit&apos;s purpose, usually in 50 characters or less. If you use VS Code, the built-in commit menu (the third button down in the sidebar) will do this check for you. This limit should be a check for yourself on how focused the content of the commit actually is. For example, if you were committing a bug fix for an out-of-place profile picture in Internet Explorer, a solid message might be:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git commit -m &quot;fix profile picture position on homepage IE&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Spacing out your commits&lt;/h3&gt;&lt;p&gt;Yes, that was a pretty easy example to contrive. Commits can easily span many more files and many more features. Always try to fit within the character limit as well as you can, but if there&apos;s too much to explain, it might be worth breaking up the commits into smaller chunks.&lt;/p&gt;&lt;p&gt;This can sounding daunting at first, but Git offers a handy tool for this when you&apos;re staging everything for a commit: &lt;code&gt;git add -p&lt;/code&gt;. Rather than staging all your changes at once, this allows you to walk through each change to the codebase you&apos;ve made file by file, giving you options to stage at each step. Here&apos;s a sample output staging some ESLint configuration files:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/7im4h1ai8ypah5prj31y.png&quot; alt=&quot;Example output of git add -p&quot; /&gt;&lt;/p&gt;&lt;p&gt;Here, we see a script added to the file &lt;code&gt;package.json&lt;/code&gt;, along with a number of ways to respond for staging. If you&apos;re unsure about what all those random letters mean, just type the &quot;?&quot;.&lt;/p&gt;&lt;p&gt;This can be a tedious process for larger commits, but is super helpful when just starting out with Git. It&apos;s also safest to &lt;strong&gt;commit like a madman&lt;/strong&gt; so your changes are easy to summarize, and you won&apos;t have to do this walk(through) of shame as often.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Extended reading: Much of this section comes from experience and various Stack Overflow sources, but &lt;a href=&quot;https://who-t.blogspot.com/2009/12/on-commit-messages.html&quot;&gt;this&lt;/a&gt; is a great long-form read on quality commit messages.&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;Pull request best practices&lt;/h2&gt;&lt;p&gt;Everything about Git we&apos;ve talked about so far has been pretty standard fare: you read through an issue, you make a branch, you commit code to it, you push it up. It&apos;s easy at this point to push everything to the development branch and call it a day... but hang on a minute! If you&apos;re working on a team, it&apos;s probably best to have someone else review the changes you&apos;ve made before merging them in.&lt;/p&gt;&lt;p&gt;The first best practice for pull requests are to... just do them 🤷 Whenever an issue is resolved by the branch you&apos;ve created and your changes are pushed up, whip up a PR.&lt;/p&gt;&lt;p&gt;There admittedly isn&apos;t a great way to do this from the terminal, so it&apos;s likely easiest from GitHub directly. Note that the purpose of a PR is to allow others to review your changes before merging into another branch (most always the development branch), so be sure &lt;strong&gt;you have already rebased the remote &lt;code&gt;master&lt;/code&gt; / &lt;code&gt;develop&lt;/code&gt; branch onto yours&lt;/strong&gt; to make reviewing changes a bit easier.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Note: If you are unfamiliar with rebasing, it is essentially merging but with an adjustment to the commit history. With rebasing, all commits in the remote will be placed before your own commits, making it seem like your branch is building off of the new remote. &lt;a href=&quot;https://medium.com/datadriveninvestor/git-rebase-vs-merge-cc5199edd77c&quot;&gt;This article&lt;/a&gt; has a nice visual aid for showing how that works.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Once the pull request is made, add people for review so they will get notified. It&apos;s usually best to reach for people who did not work on the change with you so they can review your approach with a fresh set of eyes. However, if you &lt;a href=&quot;https://www.agilealliance.org/glossary/pairing/&quot;&gt;are a fan of pair programming&lt;/a&gt;, it&apos;s possible to bypass the review process after having a solid back-and-forth with your partner.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/srgwp1ttv0ho7fetgxmb.png&quot; alt=&quot;Example of a good pull request&quot; /&gt;&lt;/p&gt;&lt;p&gt;Here&apos;s an example of what I&apos;d consider a good pull request. First, note that the team&apos;s lead was requested for review (shown by the checkmark in the &quot;reviewers&quot; section). Also note that the comment box offers a detailed breakdown of what changes were made in the branch. This can be structured however you choose, but it&apos;s best to at least:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Include the issue number so the reader can reference what is being resolved&lt;/li&gt;&lt;li&gt;Write a bulleted / numbered breakdown of each feature added, or changes made to resolve a bug&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Below this, there is a log of each commit made in the branch requested for merging. This shows how important quality commit messages are!&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Let CSS frameworks empower you, not control you</title><link>https://bholmes.dev/blog/let-css-frameworks-empower-you-not-control-you/</link><guid isPermaLink="true">https://bholmes.dev/blog/let-css-frameworks-empower-you-not-control-you/</guid><description>CSS frameworks are super useful for the building blocks of a website, but when is it best to start adding custom styles?</description><pubDate>Mon, 29 Apr 2019 14:54:27 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;Many articles tackling the issue of CSS frameworks either argue &lt;a href=&quot;https://dev.to/teamxenox/do-we-really-need-a-css-framework-4ma6&quot;&gt;whether or not they should be used in a project at all&lt;/a&gt;, or dig deep on the pitfalls of each potential option. From my experience, there&apos;s a bit more nuance to it than that. CSS frameworks have a clear place to take care of all the hefty CSS logic that grows hard to maintain, like accessibility concerns, modal / pop-up visiblity, desktop vs. mobile nav bars, and so on.&lt;/p&gt;&lt;p&gt;But where do you draw the line on what should be framework-infused and what should be custom? More to the point, how do you stop feeling so controlled by the framework that nothing you make has that special touch?&lt;/p&gt;&lt;h2&gt;My philosophy&lt;/h2&gt;&lt;p&gt;I had to encounter this issue recently when joining a project team in the Bits of Good organization at my university, Georgia Tech (go yellow jackets! 🐝). I inherited a project a semester in progress that needed some serious wind in its sails to hit the finish line, but there was one little hitch: the site was thoroughly entrenched in &lt;a href=&quot;https://getbootstrap.com/&quot;&gt;Bootstrap&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;I personally &lt;strong&gt;love&lt;/strong&gt; whipping up small design systems for new projects, so switching to a CSS framework felt like a bit of a chore at first. However, I found some clear utility in it: it was now much easier to, say, include popover modals with background fade, or use decent buttons and dropdowns without worrying about tab focus. With this, I found the best way to reconcile my world of customize-all-the-things with my team&apos;s lego-bricking approach was to &lt;strong&gt;treat frameworks as a utility.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;CSS frameworks shouldn&apos;t be the driving force behind how elements  look and feel, nor should they govern the flexibility of the site at different screen sizes. Rather, they should be a place to grab those little bits and pieces when custom solutions stop making sense. I understand that this philosophy is hard for some to justify when they need that homepage done by yesterday; still, I think this can work at any level with a little adjustment of where the custom stops and where the framework utility begins. With that, here are the main points I want to expand on.&lt;/p&gt;&lt;h2&gt;Don&apos;t rely on cookie-cutter layouts.&lt;/h2&gt;&lt;p&gt;This is a pretty major problem I&apos;ve seen, as fairly simple layouts get overly complicated by the constraints of framework layout styling. Though recent efforts like &lt;a href=&quot;https://semantic-ui.com/examples/responsive.html&quot;&gt;Semantic UI&lt;/a&gt; have done a better job of adding flexibility to the fold, there are still some severe rough edges that can make writing markup take longer than necessary.&lt;/p&gt;&lt;p&gt;I saw an example of this recently when a teammate was working on a pretty straightforward screen for our app: an applicant info view. This was just meant to be a list of headers and paragraphs to describe someone&apos;s name, email, address, personal bio, and so on. However, the curse of Bootstrap caused the document to become... this.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Container&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;Row&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;Col&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;Heading&lt;/span&gt;&lt;span&gt;&amp;gt;{`${applicant.bio.first_name} ${applicant.bio.last_name}`}&amp;lt;/&lt;/span&gt;&lt;span&gt;Heading&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;Col&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;Row&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;Row&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;Col&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;h5&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;Label&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;email&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;Email&amp;lt;/&lt;/span&gt;&lt;span&gt;Label&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;h5&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;content&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;{applicant.bio.email}&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;Col&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;Col&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;h5&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;Label&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;phoneNumber&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;Phone Number&amp;lt;/&lt;/span&gt;&lt;span&gt;Label&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;h5&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;content&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;{applicant.bio.phone_number}&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;Col&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;Col&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;h5&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;Label&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;birthDate&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;Birth Date&amp;lt;/&lt;/span&gt;&lt;span&gt;Label&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;h5&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;content&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;{applicant.bio.date_of_birth}&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;Col&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;/&lt;/span&gt;&lt;span&gt;Row&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;... a lot more row / columns&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is what I refer to as &quot;spreadsheet hell,&quot; where seemingly everything on the page feels like it has to be a row or column in some grand table defining the site. Thankfully, this is using the popular component framework React, so I can spare you the more laborous &lt;code&gt;&amp;lt;div class=&quot;col xs-4-72-57-sqrt(pi)&quot;&amp;gt;&lt;/code&gt; on every tag 😉&lt;/p&gt;&lt;p&gt;Yes, crafting containers from scratch can be tough. But once you start using &lt;a href=&quot;https://medium.freecodecamp.org/an-animated-guide-to-flexbox-d280cf6afc35&quot;&gt;Flexbox&lt;/a&gt; or &lt;a href=&quot;https://cssgridgarden.com/&quot;&gt;CSS grid&lt;/a&gt; more often, getting your HTML chunks to appear where you want can start to feel much quicker just writing the CSS directly.&lt;/p&gt;&lt;p&gt;In sum, to reduce overhead and make layouts work well, take a stab at some custom styling before letting the framework do it for you.&lt;/p&gt;&lt;h2&gt;Tweak that border radius if it feels right&lt;/h2&gt;&lt;p&gt;Another issue with CSS frameworks is how they encourage &quot;window shopping,&quot; where you start to design your website around the default components. This works for getting up and running fast, but I&apos;m sure you&apos;ve visited countless sites that feel a bit too... bootstrap-y.&lt;/p&gt;&lt;p&gt;This is a more difficult hurdle that can be framework-specific. Most offer theming options for color and font overrides, but decidedly little beyond that. So, I find it helps to start picking which components of the site can have a more unique feel, and which can stay in framework-land. This is a fairly arbitrary choice I say is best left to your design preferences and what you can build with your CSS skills. However, changing something small like adding a textured background or a call-to-action button with a dramatic drop-shadow can do a lot to make a site feel like your own.&lt;/p&gt;&lt;p&gt;Here&apos;s a simple example from our own site. We had just added content search functionality to the applicant viewer, wiring up all the frontend and backend logic to get it working how we wanted. However, presenting it to our client, we found an issue: it was unclear how to change what you are searching by. This is because we were using a default bootstrap button for the dropdown icon, so it wasn&apos;t clear how it was associated with the searchbox. So, we broke out some custom CSS to change the button&apos;s background color and push it closer to the input box, complete with a unifying border radius. The end result felt much cleaner!&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/w5cu0izo08fgnf4jmumn.gif&quot; alt=&quot;search dropdown with snazzy styling&quot; /&gt;&lt;/p&gt;&lt;h2&gt;Don&apos;t let frameworks decide your functionality&lt;/h2&gt;&lt;p&gt;This is the biggest point to drive home, since it&apos;s rare that a CSS framework will cover &lt;strong&gt;every&lt;/strong&gt; possible element you can have on your site.&lt;/p&gt;&lt;p&gt;Our team ran into this pretty early on when building out our administrator dashboard. In short, we were designing an applicant viewer where an admin could quickly switch between volunteer applications to view applicant info and change their approval status. After some discussion, we decided that something similar to an email panel might work best, with an overview of applicants and their statuses along the left, and a space for each full application on the right.&lt;/p&gt;&lt;p&gt;We certainly could have gone window-shopping through the sea of bootstrap components to find inspiration. However, this probably would have steered us towards some sub-optional pull-out menu or tab view that didn&apos;t quite hit the mark. This goes back to my point that frameworks are really a utility at the end of the day; if they can&apos;t help you out, don&apos;t reach for them!&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/gxb9hn7cxyuz980muvqv.png&quot; alt=&quot;Our final admin dashboard, complete with charmingly repetitive dummy data&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Here&apos;s our final administrator dashboard, complete with charmingly repetitive dummy data!&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;To sum up&lt;/h2&gt;&lt;p&gt;Building websites is hard. That shouldn&apos;t surprise anyone. It can be easy to see CSS frameworks as a catch-all to build a site from scratch with no experience required, but they all come with their own sets of gotchas and limiations that can hamper the quality of your site. When working on a new page, I&apos;d say just consider the following:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;What do you want this to do?&lt;/li&gt;&lt;li&gt;What navigation, layout, buttons, etc. could best help you do that?&lt;/li&gt;&lt;li&gt;Is there any overlap between that and the CSS framework you are using?&lt;/li&gt;&lt;li&gt;If so, does the styling best suit your needs, or could it use a 🌶 switch-up?&lt;/li&gt;&lt;/ol&gt;&lt;/article&gt;</content:encoded></item><item><title>A neat DIY solution to animating on scroll (for any framework)</title><link>https://bholmes.dev/blog/a-neat-diy-solution-to-animating-on-scroll-for-any-framework/</link><guid isPermaLink="true">https://bholmes.dev/blog/a-neat-diy-solution-to-animating-on-scroll-for-any-framework/</guid><description>AOS libraries sure are slick, but why add dependencies when it an be simple to implement yourself?</description><pubDate>Tue, 09 Apr 2019 15:46:47 GMT</pubDate><content:encoded>&lt;article&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Update 4.10.19&lt;/strong&gt; A recent comment below blew my mind name dropping the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API&quot;&gt;Intersection Observer API&lt;/a&gt;. This was essentially built for exactly what this article&apos;s about, allowing you to fire a callback whenever an element is in a certain position on the screen! The latter parts of the article about centralizing logic with a store are still totally applicable to this, but intersection observers clean up things a great deal over &lt;code&gt;requestAnimationFrame&lt;/code&gt; while also being more performant. However, note there is no IE support and Safari support only just recently. Alrighty, enjoy the rest of this post 😊&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Looking around the world wide web for inspiration, I&apos;ve found that many sites I love incorporate fun little &quot;reveal&quot; animations whenever I scroll to certain elements. Though subtle, these extra touches make the page feel much less static and more &lt;strong&gt;responsive&lt;/strong&gt;. The question is though... what&apos;s the best way to implement this?&lt;/p&gt;&lt;p&gt;Just scrolling through CodePen examples, I have found &lt;a href=&quot;https://codepen.io/syedrafeeq/pen/yEJKn&quot;&gt;time&lt;/a&gt; and &lt;a href=&quot;https://codepen.io/letsbleachthis/pen/zewKYE&quot;&gt;time again&lt;/a&gt; that people are reaching for catch-all libraries that can handle it for them. There are countless options out there for animating on scroll, the most prevalent being the aptly named &lt;a href=&quot;https://github.com/michalsnik/aos&quot;&gt;AOS&lt;/a&gt;. I myself was hoping to 🌶 up my site with some scroll animations, so I naturally thought to turn to AOS library for this. However, as my implementation grew more and more specialized (ex. how to I avoid loading this iFrame until I scroll to it?) I began to wonder...&lt;/p&gt;&lt;p&gt;&lt;em&gt;Can&apos;t I just build this myself?&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;Maybe. Let&apos;s see how&lt;/h2&gt;&lt;p&gt;Just starting with basic, vanilla JS and no frameworks, the approach is actually pretty simple. All we need is an &lt;code&gt;onScroll&lt;/code&gt; handler and whatever elements we actually want to animate. Starting with the basics, say we have an element of a specific ID we want to trigger an animation for. As you might imagine, we can reach for the DOM window&apos;s &lt;code&gt;onScroll&lt;/code&gt; event to figure out where our element is on screen whenever you, well, scroll:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;window.&lt;/span&gt;&lt;span&gt;onScroll&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ({&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;}) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; element&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;animate-me&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; elementTop&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; element.&lt;/span&gt;&lt;span&gt;getBoundingClientRect&lt;/span&gt;&lt;span&gt;().top&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (elementTop &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; document.body.clientHeight) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        element.classList.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;scrolled-to&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There are a few nested object attributes we need to grab for this. First, we need to get the pixel value for where the top of the element is on screen. There are a few valid ways of finding this, but through a quick internet search it seems &lt;code&gt;getBoundingClientRect()&lt;/code&gt; is the most reliable way of doing so across browsers.&lt;/p&gt;&lt;p&gt;With this, we should compare against the fixed height of the document. This is basically just the height of your browser window, being the &lt;code&gt;clientHeight&lt;/code&gt;. If the top of our element is less than this height, then some part of it must be on screen. Now, we just add our keyframe to our CSS on &lt;code&gt;.animate-me.scrolled-to&lt;/code&gt; and we&apos;re good to go 👍&lt;/p&gt;&lt;h3&gt;Okay great, we basically recreated a MDN help page example...&lt;/h3&gt;&lt;p&gt;With that out of the way, let&apos;s actually make this useable in the real world. Firstly, if you got curious and threw a &lt;code&gt;console.log&lt;/code&gt; statement in there, you likely got this whenever you twitched your scroll wheel.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://thepracticaldev.s3.amazonaws.com/i/defuaddw82tdkyrzkfpo.png&quot; alt=&quot;Console log of on scroll handler&quot; /&gt;&lt;/p&gt;&lt;p&gt;This reflects how expensive analyzing every scroll event actually is. We&apos;re executing a function for every pixel we scroll, and as we start making this function more robust, that can start causing lags and stutters.&lt;/p&gt;&lt;p&gt;One way to resolve this is using a &lt;code&gt;requestAnimationFrame&lt;/code&gt; to decide when our callback gets fired. This is another window-level function where you may queue up callbacks for the browser to call. When it feels it is ready to execute those functions without un-buttery-smoothing your scrolling experience, it will fire them off. Thankfully, this approach has seen &lt;a href=&quot;https://caniuse.com/#search=requestanimationframe&quot;&gt;relatively high browser adoption&lt;/a&gt;. All we need is a wrapper around our &lt;code&gt;onScroll&lt;/code&gt; handler to &lt;code&gt;requestAnimationFrame&lt;/code&gt;, along with a &lt;code&gt;boolean&lt;/code&gt; flag to let us know whether or not our previous callback is done executing:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; waitingOnAnimRequest &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; animChecker&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // Our old handler&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; element&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;animate-me&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; elementTop&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; element.&lt;/span&gt;&lt;span&gt;getBoundingClientRect&lt;/span&gt;&lt;span&gt;().top&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (elementTop &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; document.body.clientHeight) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        element.classList.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;scrolled-to&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;window.&lt;/span&gt;&lt;span&gt;onScroll&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; ({&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;}) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;waitingOnAnimRequest) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        window.&lt;/span&gt;&lt;span&gt;requestAnimationFrame&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            animChecker&lt;/span&gt;&lt;span&gt;(target)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            waitingOnAnimRequest &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        waitingOnAnimRequest &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Great! Now our calls should be a bit more efficient. But let&apos;s address a more pressing issue: how do we get this working for &lt;strong&gt;any&lt;/strong&gt; element in the document we may want to animate on scroll?&lt;/p&gt;&lt;p&gt;It certainly wouldn&apos;t make sense to keep adding callbacks for each possible ID or className we would need, so why not just create a centralized array we can append all of our element selectors to?&lt;/p&gt;&lt;h2&gt;Time for some loops&lt;/h2&gt;&lt;p&gt;This addition is fairly straightforward leveraging &lt;code&gt;querySelectorAll&lt;/code&gt;. Just create a global array with all selectors that should animate (either IDs or classes) and loop over them like so:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; animationSelectors &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&apos;#ID-to-animate&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;.class-to-animate&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; animChecker&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // Loop over our selectors&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    animationSelectors.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;selector&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // Loop over all matching DOM elements for that selector&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        target.&lt;/span&gt;&lt;span&gt;querySelectorAll&lt;/span&gt;&lt;span&gt;(selector).&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;element&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            const&lt;/span&gt;&lt;span&gt; elementTop&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; element.&lt;/span&gt;&lt;span&gt;getBoundingClientRect&lt;/span&gt;&lt;span&gt;().top&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            if&lt;/span&gt;&lt;span&gt; (elementTop &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; bodyHeight) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                 element.classList.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;scrolled-to&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now our scroll animation checker should be able to handle any element we throw at it!&lt;/p&gt;&lt;h2&gt;Neat! But I use X framework...&lt;/h2&gt;&lt;p&gt;...and I don&apos;t think I could use this because of Y 🤷‍♀️&lt;/p&gt;&lt;p&gt;Now hold it right there. I understand everyone&apos;s tooling has its own set of quirks, so let&apos;s try to address some of them.&lt;/p&gt;&lt;h3&gt;I use a component system, so how do I centralize this logic?&lt;/h3&gt;&lt;p&gt;Though it would be nice to have a succinct list of classes and IDs we would want to animate, components, especially with scoped CSS solutions, make it difficult to keep this list readable and expandable.&lt;/p&gt;&lt;p&gt;Thankfully, this solution just needs a single array of strings to get working, so we can use a global store each component can update with the DOM selectors they want to animate. I used this in a recent project  built on SvelteJS, which uses a subscription-based global store. To update &lt;code&gt;animationSelectors&lt;/code&gt;, I just created it as a store...&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; animationTriggers&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; writable&lt;/span&gt;&lt;span&gt;({})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;... and added the class name from whichever component when it gets created.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { animationTriggers } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;../stores&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;onMount&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    animationTriggers.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ...&lt;/span&gt;&lt;span&gt;$animationTriggers,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &apos;.wackily-animated-class&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &apos;#section-id&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ])&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This works just as well for common global state solutions like Redux and React Context as well. Redux implementations widely vary by middleware so I&apos;ll spare the multi-file example here, but here&apos;s an option using React Context (which works in vanilla React):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// store.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; AnimationTriggerContext&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; React.&lt;/span&gt;&lt;span&gt;createContext&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; StoreWrapper&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Component&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    constructor&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        super&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            selectors: []&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    render&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            // create a provider to wrap our components in at the parent level&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &amp;lt;&lt;/span&gt;&lt;span&gt;AnimationTriggerContext.Provider&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                // make our array of selectors accessible from all children&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                selectors: &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.state.selectors,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                // add a helper function to update our array&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                addSelector&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;selector&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setState&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        selectors: [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.state.selectors, selector],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }}&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                {&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.props.children}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &amp;lt;/&lt;/span&gt;&lt;span&gt;AnimationTriggerContext.Provider&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//childManyLayersDeep.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; Child&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Component&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    componentDidMount&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.context.&lt;/span&gt;&lt;span&gt;addSelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;special-class&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    render&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;special-class&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//wrap the child with a &apos;withContext&apos; so it can be accessed&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; withContext&lt;/span&gt;&lt;span&gt;(Child)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Naturally, this method is extendable to VueJS, RxJS observables, and basically everywhere else you might use a global store.&lt;/p&gt;&lt;h3&gt;Okay that&apos;s pretty nifty... but I can&apos;t use basic CSS selectors. These are components!&lt;/h3&gt;&lt;p&gt;Okay fair point; this can complicate things in most component-based frameworks. The simplest compromise is to pass a reference to the element itself in our &quot;add&quot; function instead of the class name so we can avoid DOM querying. Overall, the humble &lt;code&gt;ref&lt;/code&gt; attribute in React or Vue, rather than a class or an ID selector, should do the trick for this.&lt;/p&gt;&lt;h3&gt;Also, I&apos;m using CSS-in-JS and would rather not check for class names to start animating. What are my options?&lt;/h3&gt;&lt;p&gt;This is a fairly common pattern these days and tends to rely more on prop passing that on class name switching. Thankfully, we have pretty much all of the logic in place to figure out these props based on our store. All we need is an extra object attribute on the selector we pass in, say a &lt;code&gt;scrolledTo&lt;/code&gt; flag, which can be set &quot;true&quot; or &quot;false&quot;.&lt;/p&gt;&lt;p&gt;For this, we would modify what we add to our store to go from just a string (or ref) to an object...&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    selector&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;class-name&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    scrolledTo&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...and update its flag when scrolled to.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; animChecker&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (elementTop &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; bodyHeight) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            animationTriggers[currentIndex].scrolledTo &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we can subscribe to our array of animationTriggers (or grab the context, depending on your implementation) and pass our &lt;code&gt;scrolledTo&lt;/code&gt; flag as a prop to the component&apos;s styles.&lt;/p&gt;&lt;h2&gt;In Summary&lt;/h2&gt;&lt;p&gt;So before you protest that you could have gotten your favorite animate-on-scroll library working in the time it took to read this article... I get it. But I would say taking this feature as a fun little challenge to build out yourself is super helpful for understanding how to make sleek, efficient DOM listeners. It also means you will have one less dependency to worry about in your package, so no breaking changes and a lot of flexibility for adding new features!&lt;/p&gt;&lt;p&gt;To see this solution in action, it is used all over the place on our Georgia Tech club&apos;s homepage: Golden Swarm Games. &lt;a href=&quot;https://gsg.surge.sh/&quot;&gt;Visit the site&lt;/a&gt; or &lt;a href=&quot;https://github.com/bholmesdev/gsg-site&quot;&gt;the repo&lt;/a&gt; to see how our scroll animations work under the hood.&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Why SvelteJS may be the best framework for new web devs</title><link>https://bholmes.dev/blog/why-sveltejs-may-be-the-best-framework-for-new-web-devs/</link><guid isPermaLink="true">https://bholmes.dev/blog/why-sveltejs-may-be-the-best-framework-for-new-web-devs/</guid><description>Every new programmer starts with vanilla JS and DOM manipulation. But in the component-driven world of modern web dev, is that the best way to start?</description><pubDate>Tue, 19 Mar 2019 22:03:49 GMT</pubDate><content:encoded>&lt;article&gt;&lt;p&gt;Any web dev who&apos;s been at it for a few years has likely heard this question every other day.&lt;/p&gt;&lt;p&gt;&lt;em&gt;I&apos;m really interested in learning about web development, but I don&apos;t know how to start. Any suggestions?&lt;/em&gt;&lt;/p&gt;&lt;p&gt;A decade ago, this would have been an easy answer. Just make an &lt;code&gt;index.html&lt;/code&gt;, throw some tags in there, make that header turn red with CSS, and reach for JQuery (or plane ole JavaScript, depending on who you ask) to handle those mouse clicks!&lt;/p&gt;&lt;p&gt;...Oh, how things have changed. Now we&apos;re running around with build tools, client side routing, special frameworks with fancy runtime scripts, binding &quot;this&quot; everywhere, template literals, CSS-in-JS... how do we choose what matters most? 🤷‍♀️ We can&apos;t start teaching how React uses a virtual DOM if someone doesn&apos;t even know what the DOM is!&lt;/p&gt;&lt;p&gt;This has led to countless avenues to start with with varying degrees of &quot;just type this now, and I&apos;ll explain later.&quot; Some encourage beginners to just learn React or Vue right away to get started with modern practices, while others scream from the mountaintops that &lt;strong&gt;beginners should always start with the fundamentals.&lt;/strong&gt; Truthfully, there are merits to both approaches. The former can get newbies excited with hot reloading and components, running the risk of leaving too much to the unknown, while the latter gets beginners understanding how DOM manipulation works under the hood, while possibly steering people away with the complexities of JS we&apos;ve since abstracted away.&lt;/p&gt;&lt;p&gt;What we need, then, is a middle ground. A way to get started with the fundamentals while soaking up modern concepts like component-driven development, scoped vs. cascading CSS, templating, declarative functions, etc etc etc.&lt;/p&gt;&lt;p&gt;##Enter: Svelte&lt;/p&gt;&lt;p&gt;SvelteJS is a pretty new kid on the block just starting to get some attention. Some may know it as the &lt;a href=&quot;https://2018.stateofjs.com/front-end-frameworks/other-libraries/&quot;&gt;most popular write-in for the State of JavaScript 2018&lt;/a&gt;. For the abridged explanation, Svelte is  meant to be the framework that isn&apos;t really a framework; it&apos;s basically a tool to compile components down at the build step, allowing you to load a single &lt;code&gt;bundle.js&lt;/code&gt; on your page to render your app. This means no virtual DOM, no frameworks on top of frameworks, and &lt;strong&gt;no framework to load at runtime&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;These are pretty big selling points for more experienced developers, but most beginners probably couldn&apos;t read that last paragraph without their head exploding. Luckily, it&apos;s not this compilation sorcery that makes Svelte so beginner-friendly. It&apos;s actually the syntax.&lt;/p&gt;&lt;p&gt;If you&apos;ve never seen a Svelte component before, here&apos;s a really basic example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;pretty&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;Here&apos;s some markup &amp;lt;&lt;/span&gt;&lt;span&gt;strong&lt;/span&gt;&lt;span&gt;&amp;gt;written by {name}!&amp;lt;/&lt;/span&gt;&lt;span&gt;strong&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /* here&apos;s some scoped CSS */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    .pretty&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        color&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;red&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /* ...and a variable we can access in the markup */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Ben&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&apos;s unpack this a little. So first off, it&apos;s worth noting that all of this can live inside a regular &lt;code&gt;.html&lt;/code&gt; file, or a &lt;code&gt;.svelte&lt;/code&gt; file if your heart desires. Also, we see some familiar tags reminiscent of framework-less development: &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;. Sadly, writing styles and JS in these tags is necessary for properly building scoped components, but it allows syntax highlighting to work without extra text editor extensions like CSS-in-JS solutions. Plus, Svelte&apos;s build step is smart enough to scope any component-specific styles by default, so you won&apos;t have styles bleeding between components.&lt;/p&gt;&lt;p&gt;You&apos;ll also see some magic happening with that JavaScript variable called &lt;code&gt;name&lt;/code&gt;. This is a shiny new concept for Svelte v3, where &lt;strong&gt;any&lt;/strong&gt; variable in your component&apos;s script is accessible from markup. Thus, there&apos;s no framework specific syntax to learn for state management, so no Angular &lt;code&gt;$scope&lt;/code&gt;, no React &lt;code&gt;this.state&lt;/code&gt;, and no Vue &lt;code&gt;data&lt;/code&gt;. Instead, we can just use &lt;code&gt;let&lt;/code&gt;s everywhere to get assignable state values, cuing re-renders whenever these values change.&lt;/p&gt;&lt;p&gt;This freedom from this jargon means making a component can almost feel like whipping up a CodePen, but without wondering how to connect that declarative JS function you learned to some DOM query selector. Don&apos;t worry too much though: Svelte won&apos;t mess with callback functions or window listeners, so those fundamentals remain.&lt;/p&gt;&lt;p&gt;The other nice thing about these components is that they&apos;re just as import-able as a traditional component. Just import the &lt;code&gt;.html&lt;/code&gt; and Svelte knows how to unwrap it 😊&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;Wizardry&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    import&lt;/span&gt;&lt;span&gt; Wizardry &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;./wizardry.html&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Neat, but hang on a minute...&lt;/h3&gt;&lt;p&gt;Some readers may find this concept as mind-blowing as I do, but others probably have their pitchforks ready at the thought of throwing this at beginners. Won&apos;t this confuse them about how DOM manipulation really works?&lt;/p&gt;&lt;p&gt;The answer is... maybe. But when someone&apos;s just starting out (at least from personal experience), it&apos;s okay to accept a little abstraction for the sake of making cool things more quickly.&lt;/p&gt;&lt;p&gt;Also, just as languages like Java and JS have abstracted away pointer management with garbage collection, it feels like most every modern web development tool has abstracted away DOM manipulation, save for more advanced edge cases beginners likely won&apos;t need to face. Btw, if you are scratching your head at what pointer management is, I think that kind of proves my point 😛 Thus, rather than forcing beginners to manipulate the DOM or grasping framework-specific state wrappers, why not just let them access variables directly from markup? Now they can learn the basic principles of component state without getting caught in the weeds.&lt;/p&gt;&lt;h2&gt;Okay, but how much &quot;special syntax&quot; is there?&lt;/h2&gt;&lt;p&gt;There&apos;s some templating to pick up here admittedly, but it&apos;s a lot less than you might think! One Svelte-y syntax is for looping and conditionals for displaying DOM elements. This works a lot like the JSX way of returning elements from a &lt;code&gt;map&lt;/code&gt;, but without all the nested brackets beginners (and myself) can easily get lost in. Here&apos;s a basic one that generates a list of a elements from an array:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;ul&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    {#&lt;/span&gt;&lt;span&gt;each&lt;/span&gt;&lt;span&gt; profiles &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; profile}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt;&amp;gt;{profile.name}: {profile.role}&amp;lt;/&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    {/&lt;/span&gt;&lt;span&gt;each&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;ul&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; profiles&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {name: &lt;/span&gt;&lt;span&gt;&apos;Wes Bos&apos;&lt;/span&gt;&lt;span&gt;, role: &lt;/span&gt;&lt;span&gt;&apos;React extraordinaire&apos;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {name: &lt;/span&gt;&lt;span&gt;&apos;Chris Coyier&apos;&lt;/span&gt;&lt;span&gt;, role: &lt;/span&gt;&lt;span&gt;&apos;Father of CodePen&apos;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {name: &lt;/span&gt;&lt;span&gt;&apos;Cassidy Williams&apos;&lt;/span&gt;&lt;span&gt;, role: &lt;/span&gt;&lt;span&gt;&apos;Letting you know it&apos;&lt;/span&gt;&lt;span&gt;s pi time&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Again, I understand any criticisms about the syntax, but what I love is how easily understood it is. Instead of nesting JavaScript in our HTML, we just say hey, lemme loop over this array and create an element for each one.&lt;/p&gt;&lt;p&gt;There&apos;s another Svelte oddity worth mentioning that I&apos;m admittedly not as thrilled about: passing props to components. Yes, it is fully functional and easy to learn, but the syntax is a bit too magical for some people&apos;s tastes. To handle props, we simply pass the prop to the component wherever it&apos;s imported...&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;!-- somewhere.html --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Profile&lt;/span&gt;&lt;span&gt; coolGuy&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Scott Tolinski&quot;&lt;/span&gt;&lt;span&gt; /&amp;gt; &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;...and get that variable as an exported &quot;let&quot;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;!-- profile.html --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;{coolGuy}&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    export&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; coolGuy &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &apos;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I totally understand if some are turned off by abusing &quot;export&quot; like this, but it does at least follow the way beginners should conceptualize modules: we export what we should access outside of the component, and import what we want to show in our component.&lt;/p&gt;&lt;h2&gt;What about the build process though?&lt;/h2&gt;&lt;p&gt;Another criticism about getting beginners started with frameworks is the need to use a package manager. Which means... &lt;em&gt;gasp&lt;/em&gt; using the terminal!&lt;/p&gt;&lt;p&gt;Look, I get it, popping that thing open can be intimidating, with its monospace font and that spooky &quot;cd&quot; to jump directories. But to be fair, it&apos;s really not a huge hurdle when you only need a single command to get running. Additionally, the &lt;a href=&quot;https://code.visualstudio.com/docs/editor/integrated-terminal&quot;&gt;integrated terminal within VS Code&lt;/a&gt; makes it dead simple to get started with; it even plops you down in your current project directory!&lt;/p&gt;&lt;p&gt;Svelte actually offers &lt;a href=&quot;https://v3.svelte.technology/repl?version=3.0.0-beta.20&amp;amp;example=hello-world&quot;&gt;a downloadable starting point&lt;/a&gt; that&apos;s nice outside of the box, but I &lt;a href=&quot;https://github.com/bholmesdev/svelte-starter-template&quot;&gt;made my own starter template&lt;/a&gt; that just uses the build tool Rollup for live reloading. In fact, the config file is under 30 lines long! For a basic Svelte project, these is all the directories and files you need:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/public&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    index.html&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/src&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   index.html&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;rollup.config.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;package.json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Just add a command to run the build step in the &lt;code&gt;package.json&lt;/code&gt; and you&apos;re all set! You could certainly say that all the extra modules and files other frameworks need aren&apos;t a problem if beginners don&apos;t touch them, but in my eyes, the less extra stuff for newbies to wonder about, the better.&lt;/p&gt;&lt;h2&gt;But is it a stable framework?&lt;/h2&gt;&lt;p&gt;This is a very relevant question for a framework as young as Svelte. All examples I have shown use the syntax of Svelte version 3, which &lt;s&gt;is still in beta as of the time of this writing&lt;/s&gt; has a relatively small following compared to framework behemoths like ReactJS and VueJS. So as exciting as it is, I would wait another few months before rushing to teach code workshops with it. Still, Svelte offers a &lt;a href=&quot;https://svelte.dev/docs&quot;&gt;really concise page for documentation&lt;/a&gt; for version 3 that can ease beginners into the framework without getting overwhelmed by subpage after subpage of explanation.&lt;/p&gt;&lt;p&gt;So let&apos;s go over some of the main selling points for learning web development with Svelte:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;It&apos;s a component-based framework with 0 extra plugins&lt;/li&gt;&lt;li&gt;It handles state management without all the usual cruft&lt;/li&gt;&lt;li&gt;It uses scoped styling without needing CSS-in-JS (so no editor extensions or wacky syntax)&lt;/li&gt;&lt;li&gt;It only needs a dead simple build script to get going&lt;/li&gt;&lt;li&gt;Hardly any files are needed in a base project&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Of course, it&apos;s totally fine if I haven&apos;t convinced you with this post; all good posts stoke a little controversy! But I hope it at least showed you how freaking cool and simple Svelte can be to learn.&lt;/p&gt;&lt;/article&gt;</content:encoded></item><item><title>Why I&apos;m using Surge and not GitHub Pages</title><link>https://bholmes.dev/blog/why-im-using-surge-and-not-github-pages/</link><guid isPermaLink="true">https://bholmes.dev/blog/why-im-using-surge-and-not-github-pages/</guid><description>Everyone&apos;s making a static site these days, and most use GitHub Pages to get up and running fast. Here&apos;s why Surge is also worth a look 👀</description><pubDate>Wed, 06 Mar 2019 22:40:16 GMT</pubDate><content:encoded>&lt;article&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; I&apos;ve noticed that Surge hasn&apos;t seen many updates to its repo recently. It seems safe to use for now (I haven&apos;t seen any vulnerability warnings), but if the project ends dying off, &lt;a href=&quot;https://www.netlify.com/docs/cli/&quot;&gt;Netlify&lt;/a&gt; and &lt;a href=&quot;https://zeit.co/now&quot;&gt;Zeit Now&lt;/a&gt; are &lt;strong&gt;great&lt;/strong&gt; alternatives that offer similar benefits over GitHub Pages&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;Hm, so what&apos;s a &quot;surge&quot; exactly? 🤔&lt;/h2&gt;&lt;p&gt;GitHub Pages is so prolific at this point it likely needs no introduction. &lt;a href=&quot;https://github.com/sintaxi/surge&quot;&gt;Surge&lt;/a&gt;, on the other hand, is a much lower profile project. It accomplishes the same goal as GitHub Pages, allowing one to publish their static site on an accessible URL of whatever name they choose complete with an &quot;https.&quot; Where surge differs is in how that website gets put on the world wide web.&lt;/p&gt;&lt;p&gt;Surge lets you get from 0 to published in the command line without any extra setup. Once you install the package, you can use their CLI in your project directory to whip up an account...&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;👉 surge&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    Welcome to surge!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    Please login or create an account by entering email and password:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    email: jamesKPolk@napoleonofthestump.gov&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    password: &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;enter your domain to deploy to...&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;    domain: forgottenpresidents.club&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and boom 💥! You&apos;re up and running. Note I&apos;m using a custom URL extension here as well. You can of course specify this if you have the rights to that domain, but you can omit the extension to use the totally free &lt;code&gt;surge.sh&lt;/code&gt;.&lt;/p&gt;&lt;h2&gt;Looks easy, but where are my git hooks?&lt;/h2&gt;&lt;p&gt;The beauty of GitHub Pages is it will simply track your master branch and rebuild / publish your site whenever it gets updated. Thankfully, surge offers the option to hook into pushes and commits with a little addition to your &lt;code&gt;package.json&lt;/code&gt;. This snippet will do the trick for redeploying on push:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&quot;devDependencies&quot;: { &quot;surge&quot;: &quot;latest&quot;, &quot;git-scripts&quot;: &quot;0.2.1&quot; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;git&quot;: { &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;scripts&quot;: { &quot;pre-push&quot;: &quot;surge --project ./ &lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        --domain forgottenpresidents.surge.sh&quot; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;       }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Neat. But what are the benefits exactly?&lt;/h2&gt;&lt;p&gt;Surge offers a few niceties that GitHub Pages won&apos;t out of the box. For one, you obviously don&apos;t need to use GitHub to manage your version control, so you can use any custom solution you desire and still deploy with the same &lt;code&gt;surge&lt;/code&gt; command.&lt;/p&gt;&lt;p&gt;Another is an advantage that &lt;a href=&quot;https://zeit.co/now&quot;&gt;Zeit Now&lt;/a&gt; also fails to address: client side routing fallbacks.&lt;/p&gt;&lt;p&gt;Extending our &lt;a href=&quot;https://www.youtube.com/watch?v=StTiCU_fqCg&quot;&gt;James K. Polk&lt;/a&gt; example, say we deployed a simple project folder with a few &lt;code&gt;html&lt;/code&gt; files:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;    ./build&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - index.html&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - what_i_stand_for.html&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - donate_now.html&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - 200.html&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notice our &lt;code&gt;200.html&lt;/code&gt; added at the bottom. This acts as a fallback to serve up whenever a given route is invalid. Assuming these are all the &lt;code&gt;html&lt;/code&gt; files in our build, we know someone visiting, say, forgottenpresidents.club/sendfanmail would get a &lt;code&gt;404&lt;/code&gt; since that file doesn&apos;t exist. However, since we have a &lt;code&gt;200.html&lt;/code&gt;, Surge knows to just serve up this page instead since it&apos;s a fallback. This is more flexible than the traditional &lt;code&gt;404.html&lt;/code&gt; to catch errors since we sometimes don&apos;t want certain paths to result in a &lt;code&gt;404&lt;/code&gt;, like a dynamic username in the URL that gets handled in JavaScript but should always route to the same base page. This makes routing a bit cleaner whether you&apos;re using a library like React router or just tapping into the browser&apos;s history API on your own, since we don&apos;t have to deal with redirects anymore 🎉&lt;/p&gt;&lt;p&gt;The last major benefit is one shared by Zeit Now: deploying whenever you want in a matter of seconds. Rather than dirtying up your commit history, you can just deploy your changes instantly and see how they look. This was super useful recently when testing out a mobile layout for a site I was developing, where mobile Safari was being a bit more finicky than my mobile viewer in Chrome. I could just deploy my changes to whatever URL I chose and see my edits as soon as I refreshed the page!&lt;/p&gt;&lt;h2&gt;Okay I&apos;m starting to see it. What are the limitations?&lt;/h2&gt;&lt;p&gt;So Surge was built to be really good at doing a very specific task. If you have anything more than a static site or client side application, there&apos;s sadly not much you can do with this tool. Any fancy backend you have would need to be deployed separately, or you can use the aforementioned Zeit Now to deploy everything in one place. Now is a much more flexible tool that&apos;s quickly gaining traction, but I&apos;ve stayed loyal to Surge because of the routing niceties and lack of any necessary &lt;code&gt;config&lt;/code&gt; files.&lt;/p&gt;&lt;p&gt;Of course you should use whatever tool that works best in your workflow. But the next time you&apos;re building v175 of your portfolio site, consider giving Surge a try 😊&lt;/p&gt;&lt;/article&gt;</content:encoded></item></channel></rss>