<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hack4Impact</title>
    <description>The latest articles on DEV Community by Hack4Impact (@hack4impact).</description>
    <link>https://dev.to/hack4impact</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F3132%2F1d0025ce-a2e9-4a8d-9751-116e0b0ccf97.png</url>
      <title>DEV Community: Hack4Impact</title>
      <link>https://dev.to/hack4impact</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hack4impact"/>
    <language>en</language>
    <item>
      <title>Another way to understand JavaScript's array.reduce</title>
      <dc:creator>Ben Holmes</dc:creator>
      <pubDate>Fri, 05 Feb 2021 16:26:05 +0000</pubDate>
      <link>https://dev.to/hack4impact/another-way-to-understand-array-reduce-5757</link>
      <guid>https://dev.to/hack4impact/another-way-to-understand-array-reduce-5757</guid>
      <description>&lt;p&gt;If you've run the guantlet of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#instance_methods" rel="noopener noreferrer"&gt;array methods&lt;/a&gt; in JavaScript, you'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's Hack4Impact chapter (&lt;a href="http://hack4impact.org/dev-bootcamp" rel="noopener noreferrer"&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'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's for you. You can &lt;a href="https://codepen.io/bholmesdev/pen/rXJpmr?editors=0010" rel="noopener noreferrer"&gt;fork this CodePen&lt;/a&gt; for the source material to follow along 🏃‍♂️&lt;/p&gt;

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

&lt;h2&gt;
  
  
  📝 Step-by-step cheatsheet
&lt;/h2&gt;

&lt;p&gt;Let's walk our way to &lt;code&gt;reduce&lt;/code&gt; by using what we know: good ole' for loops.&lt;/p&gt;

&lt;p&gt;Here'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's a simplified approach for what we want to do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// make a variable to keep track of the length, starting at 0&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;albumLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="c1"&gt;// walk through the songs on the album...&lt;/span&gt;
&lt;span class="nx"&gt;album&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;song&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// and add the length of each song to our running total&lt;/span&gt;
  &lt;span class="nx"&gt;albumLength&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;song&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minutesLong&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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'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 "accumulate" is pretty significant here though. In essence, we'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'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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;albumLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="nx"&gt;album&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;song&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;albumLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;albumLength&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;song&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minutesLong&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// accumulator up here 👇&lt;/span&gt;
&lt;span class="nx"&gt;album&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;albumLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;song&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;albumLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;albumLength&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;song&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minutesLong&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 👈 initial value here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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't too different conceptually, since we're still adding our song length onto our "accumulated" album length:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;album&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;albumLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;song&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 👇 Use "return" instead of our = assignment&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;albumLength&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;song&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minutesLong&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalAlbumLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;album&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;albumLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;song&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;albumLength&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;song&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minutesLong&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;And that'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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Say we have this array of arrays,&lt;/span&gt;
&lt;span class="c1"&gt;// and we want to "flatten" everything to one big array of songs&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;songsByAlbum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Rap Snitches Knishes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Beef Rap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Gumbo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accordion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Meat Grinder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Figaro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fazers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Anti-Matter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Krazy World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;songs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="nx"&gt;songsByAlbum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;albumSongs&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// "spread" the contents of each array into our big array using "..."&lt;/span&gt;
  &lt;span class="nx"&gt;songs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;albumSongs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn'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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Ex. Make these MF DOOM songs titles all caps&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;songs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="nx"&gt;songsByAlbum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;albumSongs&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;songs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;albumSongs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uppercaseSongs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;song&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;song&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUppercase&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fthinking-about-array-reduce%2Fmf-doom-finger-guns.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fthinking-about-array-reduce%2Fmf-doom-finger-guns.gif" alt="MF DOOM giving finger guns"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;All caps when you spell the man name. Rest in piece &lt;a href="https://open.spotify.com/artist/2pAWfrd7WFF3XhVt9GooDL?si=McgDfNnbTJK5yIdKB_EHGQ" rel="noopener noreferrer"&gt;MF DOOM&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is fine, but what if we could "chain" these 2 modifications &lt;em&gt;together&lt;/em&gt;?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// grab our *final* result all the way at the start&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uppercaseSongs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Rap Snitches Knishes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Beef Rap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Gumbo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accordion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Meat Grinder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Figaro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fazers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Anti-Matter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Krazy World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;// rewriting our loop to a "reduce," same way as before&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;albumSongs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;albumSongs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="c1"&gt;// then, map our songs right away!&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;song&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;song&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUppercase&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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'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;h2&gt;
  
  
  Learn a little something?
&lt;/h2&gt;

&lt;p&gt;Awesome. In case you missed it, I launched an &lt;a href="https://tinyletter.com/bholmesdev" rel="noopener noreferrer"&gt;my "web wizardry" newsletter&lt;/a&gt; to explore more knowledge nuggets like this!&lt;/p&gt;

&lt;p&gt;This thing tackles the &lt;a href="https://www.swyx.io/first-principles-approach/" rel="noopener noreferrer"&gt;"first principles"&lt;/a&gt; of web development. In other words, what are all the janky browser APIs, bent CSS rules, and semi-accessible HTML that make all our web projects tick? If you're looking to go &lt;em&gt;beyond the framework&lt;/em&gt;, this one's for you dear web sorcerer 🔮&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tinyletter.com/bholmesdev" rel="noopener noreferrer"&gt;Subscribe away right here&lt;/a&gt;. I promise to always teach and never spam ❤️&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>A shiny-on-hover effect that follows your mouse (CSS) ✨</title>
      <dc:creator>Ben Holmes</dc:creator>
      <pubDate>Thu, 28 Jan 2021 17:40:13 +0000</pubDate>
      <link>https://dev.to/hack4impact/a-shiny-on-hover-effect-that-follows-your-mouse-css-4d5b</link>
      <guid>https://dev.to/hack4impact/a-shiny-on-hover-effect-that-follows-your-mouse-css-4d5b</guid>
      <description>&lt;p&gt;Hover states are probably the most fun a developer can have when a designer isn't looking. You've seen the basics at this point; fade-ins, growing and shrinking, color shifts, &lt;a href="https://www.joshwcomeau.com/react/rainbow-button/" rel="noopener noreferrer"&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="https://www.youtube.com/watch?v=VBkGe1TxEuI&amp;amp;t=225s" rel="noopener noreferrer"&gt;to Keyframers&lt;/a&gt; for shouting it out!)&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1278384065087893505-154" src="https://platform.twitter.com/embed/Tweet.html?id=1278384065087893505"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1278384065087893505-154');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1278384065087893505&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;This isn't some "static" 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="https://hack4impact.org" rel="noopener noreferrer"&gt;Hack4Impact&lt;/a&gt; site 😁&lt;/p&gt;

&lt;p&gt;So let'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;strong&gt;&lt;em&gt;Onwards!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Our end goal
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fcss-shiny-buttons%2Fshiny-button-demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fcss-shiny-buttons%2Fshiny-button-demo.gif" alt="Demo of buttons shining on hover on hack4impact.org website"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The effect is pretty simple on the surface. Just shift the color a little bit whenever you hover over the button, plus a little circular gradient for a "metallic" sheen. &lt;/p&gt;

&lt;p&gt;But there's a bit of added spice that CSS can't pull off on its own: &lt;strong&gt;We need to track your cursor position&lt;/strong&gt; to make this interactive! Luckily, this has gotten a lot easier over the years; You won't even need a UI framework or state management to pull it off 👀&lt;/p&gt;

&lt;h2&gt;
  
  
  🎈 Brief primer on CSS variables
&lt;/h2&gt;

&lt;p&gt;In case you haven't heard, CSS variables are kind of taking web development by storm right now. They're a bit like those &lt;code&gt;$&lt;/code&gt; variables preprocessors like &lt;a href="https://sass-lang.com" rel="noopener noreferrer"&gt;SASS&lt;/a&gt; and &lt;a href="http://lesscss.org" rel="noopener noreferrer"&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's see a simple example. Say we want to make a balloon pump, where you hit a button as fast as you can to "inflate" an HTML-style balloon.&lt;/p&gt;

&lt;p&gt;If we didn't know anything about CSS variables, we'd probably do some style manipulation straight from JavaScript. Here's how we'd pump up a balloon using the &lt;code&gt;transform&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;balloon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.balloon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// make the balloon bigger by 50%&lt;/span&gt;
&lt;span class="nx"&gt;balloon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scale(1.5)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, to make the balloon just a little bit bigger on every button click:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pump&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.pump&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// keep track of the balloon's size in a JS variable&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;pump&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;balloon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`scale(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There'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'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'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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;pump&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;balloon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--size&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, slide that variable into our &lt;code&gt;transform&lt;/code&gt; property from the CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.balloon&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* set a default / starting value if JS doesn't supply anything */&lt;/span&gt;
  &lt;span class="py"&gt;--size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;...&lt;/span&gt;
  &lt;span class="c"&gt;/* use var(...) to apply the value */&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;pump&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Note: you *can't* use balloon.style here!&lt;/span&gt;
  &lt;span class="c1"&gt;// This won't give you the up-to-date value of your variable.&lt;/span&gt;
  &lt;span class="c1"&gt;// For that, you'll need getComputedStyle(...)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getComputedStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;balloon&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getPropertyValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--size&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// size is a string at this stage, so we'll need to cast it to a number&lt;/span&gt;
  &lt;span class="nx"&gt;balloon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--size&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There'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'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's a fully working example to &lt;em&gt;pump&lt;/em&gt; up your confidence 🎈&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/bholmesdev/embed/vYXogza?height=600&amp;amp;default-tab=js,result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  ✨ Let's get rolling on our shiny button
&lt;/h2&gt;

&lt;p&gt;Before putting our newfound CSS variable knowledge to the test, let's jump into the styles we'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'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="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Backgrounds_and_Borders/Using_multiple_backgrounds" rel="noopener noreferrer"&gt;overlay multiple backgrounds&lt;/a&gt; on the same element!). But for the sake of simplicity, let's just add another element &lt;em&gt;inside&lt;/em&gt; our button representing our "shiny" effect. We'll do this using a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements" rel="noopener noreferrer"&gt;pseudo-element&lt;/a&gt; to be fancy 😁&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.shiny-button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* add this property to our button, */&lt;/span&gt;
  &lt;span class="c"&gt;/* so we can position our shiny gradient *relative* to the button itself */&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c"&gt;/* then, make sure our shiny effect */&lt;/span&gt;
  &lt;span class="c"&gt;/* doesn't "overflow" outside of our button */&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#3984ff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* blue */&lt;/span&gt;
  &lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.shiny-button&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* all pseudo-elements need "content" to work. We'll make it empty here */&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c"&gt;/* make sure the gradient isn't too bright */&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c"&gt;/* add a circular gradient that fades out on the edges */&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#3984ff00&lt;/span&gt; &lt;span class="m"&gt;80%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;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="https://css-tricks.com/8-digit-hex-codes/" rel="noopener noreferrer"&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;iframe height="600" src="https://codepen.io/bholmesdev/embed/LYbPjNQ?height=600&amp;amp;default-tab=css,result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  🪤 Now, lets track some mouse cursors
&lt;/h2&gt;

&lt;p&gt;We'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's easy to forget the dozens of other event listeners at our disposal! We'll need to use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/mousemove_event" rel="noopener noreferrer"&gt;&lt;code&gt;mousemove&lt;/code&gt; event&lt;/a&gt; for our purposes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.shiny-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousemove&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we log out or &lt;code&gt;event&lt;/code&gt; object, we'll find some useful values in here. The main one's we'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;iframe height="600" src="https://codepen.io/bholmesdev/embed/yLVBNzE?height=600&amp;amp;default-tab=js,result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This is pretty useful, but it's not &lt;em&gt;quite&lt;/em&gt; the info we'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'd need to set &lt;code&gt;top: 0; left: 0;&lt;/code&gt; So, we'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'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't a magical &lt;code&gt;event&lt;/code&gt; property for this, so we'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're in. There's also this neat function called &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect" rel="noopener noreferrer"&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's position from our cursor'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;iframe height="600" src="https://codepen.io/bholmesdev/embed/YzpKGbo?height=600&amp;amp;default-tab=js,result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  💅 Pipe those coordinates into CSS
&lt;/h2&gt;

&lt;p&gt;Alright, let's put two and two together here! We'll pass our values from the &lt;code&gt;mousemove&lt;/code&gt; listener:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mousemove&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--x&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--y&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we'll add some CSS variables to that shiny pseudo-element from before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.shiny-button&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;...&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;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'll use 0 for both.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CSS variables have a weird concept of "types." Here, we're assuming we'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'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'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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* to explain this selector, we're */&lt;/span&gt;
&lt;span class="c"&gt;/* selecting our ::after element when the .shiny-button is :hover-ed over */&lt;/span&gt;
&lt;span class="nc"&gt;.shiny-button&lt;/span&gt;&lt;span class="nd"&gt;:hover::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* show a faded shiny effect on hover */&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.shiny-button&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;...&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c"&gt;/* ease into view when "transitioning" to a non-zero opacity */&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;opacity&lt;/span&gt; &lt;span class="m"&gt;0.2s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boom! Just add a one-line transition effect, and we get a nice fade-in. Here's our finished product ✨&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/bholmesdev/embed/RwobPJG?height=600&amp;amp;default-tab=css,result&amp;amp;embed-version=2"&gt;
&lt;/iframe&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'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'll need to grab a &lt;strong&gt;reference&lt;/strong&gt; to the button you're shine-ifying. In React, we can use a &lt;code&gt;useRef&lt;/code&gt; hook to retrieve this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ShinyButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// null to start&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buttonRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// add a useEffect to check that our buttonRef has a value&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buttonRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;buttonRef&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;buttonRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;✨✨✨&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or in Svelte, we can &lt;code&gt;bind&lt;/code&gt; our element to a variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;onMount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;buttonRef&lt;/span&gt;
  &lt;span class="c1"&gt;// our ref always has a value onMount!&lt;/span&gt;
  &lt;span class="nf"&gt;onMount&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;bind:this=&lt;/span&gt;&lt;span class="s"&gt;{buttonRef}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;✨✨✨&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Aside: I always like including Svelte examples, since they're usually easier to understand&lt;/em&gt; 😁&lt;/p&gt;

&lt;p&gt;Once we have this reference, it's business-as-usual for our property setting:&lt;/p&gt;

&lt;h3&gt;
  
  
  React example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ShinyButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buttonRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// throw your mousemove callback up here to "add" and "remove" later&lt;/span&gt;
  &lt;span class="c1"&gt;// might be worth a useCallback based on the containerRef as well!&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mouseMoveEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;containerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;containerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;containerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--y&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buttonRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;buttonRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousemove&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mouseMoveEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// don't forget to *remove* the eventListener&lt;/span&gt;
    &lt;span class="c1"&gt;// when your component unmounts!&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;buttonRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousemove&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mouseMoveEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;buttonRef&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Svelte example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;onMount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onDestroy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;buttonRef&lt;/span&gt;
  &lt;span class="c1"&gt;// again, declare your mousemove callback up top&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mouseMoveEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buttonRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;buttonRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;buttonRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--y&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;onMount&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;buttonRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousemove&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mouseMoveEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nf"&gt;onDestroy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;buttonRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousemove&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mouseMoveEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main takeaway: 💡 &lt;strong&gt;don'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="https://codesandbox.io/s/hack4impact-website-0kn5m?file=/components/shared/Nav/index.tsx" rel="noopener noreferrer"&gt;head over here&lt;/a&gt;.&lt;/strong&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Learn a little something?
&lt;/h2&gt;

&lt;p&gt;Awesome. In case you missed it, I launched an &lt;a href="https://tinyletter.com/bholmesdev" rel="noopener noreferrer"&gt;my "web wizardry" newsletter&lt;/a&gt; to explore more knowledge nuggets like this!&lt;/p&gt;

&lt;p&gt;This thing tackles the &lt;a href="https://www.swyx.io/first-principles-approach/" rel="noopener noreferrer"&gt;"first principles"&lt;/a&gt; of web development. In other words, what are all the janky browser APIs, bent CSS rules, and semi-accessible HTML that make all our web projects tick? If you're looking to go &lt;em&gt;beyond the framework&lt;/em&gt;, this one's for you dear web sorcerer 🔮&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tinyletter.com/bholmesdev" rel="noopener noreferrer"&gt;Subscribe away right here&lt;/a&gt;. I promise to always teach and never spam ❤️&lt;/p&gt;

</description>
      <category>css</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How we built an app that uses worker location history to combat wage theft</title>
      <dc:creator>Abhinav Suri</dc:creator>
      <pubDate>Sat, 05 Dec 2020 19:43:07 +0000</pubDate>
      <link>https://dev.to/hack4impact/how-we-built-an-app-that-uses-worker-location-history-to-combat-wage-theft-1hfg</link>
      <guid>https://dev.to/hack4impact/how-we-built-an-app-that-uses-worker-location-history-to-combat-wage-theft-1hfg</guid>
      <description>&lt;p&gt;In 2008, the Center for Urban Economic Development &lt;a href="http://nelp.3cdn.net/e470538bfa5a7e7a46_2um6br7o3.pdf"&gt;surveyed&lt;/a&gt; 4,387 low-wage workers in Chicago, Los Angeles, and New York City. They wanted to determine the extent of violations of employment laws in core sectors of the US economy.&lt;/p&gt;

&lt;p&gt;They discovered that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  76% of those who worked more than 40 hours were not paid the legally required overtime rate.&lt;/li&gt;
&lt;li&gt;  68% of the sample experienced at least one pay-related violation in the previous work week.&lt;/li&gt;
&lt;li&gt;  The average worker lost $51 of his or her weekly earnings of $339 due to wage theft. This loss translates to over $2,652 in losses over one year (out of a total average salary of $17,616).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Furthermore, this study estimates that workers across the country lose a collective of $50 billion per year due to wage theft.&lt;/p&gt;

&lt;p&gt;Even though this study was conducted eight years ago, the findings are still relevant: the majority of low-wage workers are the victims of wage theft. If they want to recover their lost wages, they must turn to the courts. Because so many low-wage workers work in exploitative situations, their employers fail to keep the required records that would show the number of hours that a worker may have worked or the pay that the worker received each week. Without these records, workers must rely on their evidence as to their work hours and pay.&lt;/p&gt;

&lt;p&gt;Frequently, lawyers must rely on the clients to remember and then construct a schedule of their whereabouts for several months, which makes for a weaker case. That is where this story begins.&lt;/p&gt;

&lt;p&gt;Over the past semester, my team and I at &lt;a href="http://hack4impact.org"&gt;Hack4Impact&lt;/a&gt; had the opportunity to work with &lt;a href="http://clsphila.org"&gt;Community Legal Services of Philadelphia&lt;/a&gt; (CLS), a pro-bono legal clinic which has served over one million low-income Philadelphians since its founding in 1966.&lt;/p&gt;

&lt;p&gt;We were tasked with creating a website to analyze a client’s Google Location History and create a timesheet for all the times a client entered and exited a user-inputted workplace. The intention was to use the worker’s google location history to supplement their own testimony to provide a more solid case as to how much the client is owed. Since most potential clients carry low-cost phones (typically Android) and do not location history tracking, this solution would prove to be incredibly helpful as a starting point for reconstructing a client’s court-admissible time sheet for their case, giving CLS lawyers + paralegals a completely new source of evidence from a reliable source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZZfbraMf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A4W30SgcN-c_9LTKeMgvmBg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZZfbraMf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A4W30SgcN-c_9LTKeMgvmBg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Project User flow:
&lt;/h4&gt;

&lt;p&gt;Our optimal user flow worked out to the following (note, I am putting pictures of this from our live product for you to better visualize these requirements):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4NQHE4rC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A-3cpBWAKrItGs6aBO1DQMA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4NQHE4rC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A-3cpBWAKrItGs6aBO1DQMA.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
undefined&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A user should be able to add their LocationHistory.json file (downloaded from Google Takeout).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cMiKiw_K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AY14G8JE71UK-zAGjUWgfcQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cMiKiw_K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AY14G8JE71UK-zAGjUWgfcQ.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
undefined&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A user should then be able to have their location data processed and displayed on a map&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RAqFpkNW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AP75v5oDxY74CWzPAHPQ8AA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RAqFpkNW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AP75v5oDxY74CWzPAHPQ8AA.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
undefined&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A user should then be able to select a bounding box area containing the rough area of their work place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QrF1FKHQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AJV8XXJYStQGBrFtZbdro2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QrF1FKHQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AJV8XXJYStQGBrFtZbdro2w.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
undefined&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A user should then be able to select a start day for the week and submit the file for processing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0wbUop7c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AoNhqpzCGT9pfIqCaZuhMEQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0wbUop7c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AoNhqpzCGT9pfIqCaZuhMEQ.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
undefined&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The location history should then be processed into a .csv file. This file should have rows containing the amount of time a user spent within a bounding box for a workplace (along with start and end times).&lt;/li&gt;
&lt;li&gt;  If a user leaves and then enters a workplace, these should appear as separate rows. At the end of a week, the total amount of hours should be tabulated and displayed in a separate column.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of everything, all of this had to be done on the front end to avoid privacy issues with storing location data on our servers. These requirements alone seemed to be relatively easy to do. Little did I realize that parsing and displaying a LocationHistory.json file would probably be the most challenging task.&lt;/p&gt;
&lt;h3&gt;
  
  
  Google LocationHistory.json Structure &amp;amp; First Attempt At Loading:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lZ7fJ1bO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AczxPP4M2LYSxY8x7TOMh0Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lZ7fJ1bO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AczxPP4M2LYSxY8x7TOMh0Q.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
undefined&lt;/p&gt;

&lt;p&gt;In case you did not know, Google keeps an eye on almost everything you do. More specifically, they do keep track of your Location History if you have an android phone and have not turned it off by now. If you want, you can download your history to date by going to &lt;a href="http://takeout.google.com/settings/takeout"&gt;takeout.google.com/settings/takeout&lt;/a&gt; and downloading your file in JSON format (be warned…it can be enormous).&lt;/p&gt;

&lt;p&gt;My LocationHistory.json alone was about 59.9 MB in size (I had an Android phone for about two years), but some of the clients who would be using our system could have location histories a &lt;strong&gt;few hundred megabytes in size&lt;/strong&gt;. Trying to just load the whole JSON file into memory causes the browser to hang for approximately 30 seconds before triggering the classic “Aw Snap” error on chrome (usually indicating an out of memory error).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JTZIx8_N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2ADzE2c4KueoQpWGFP2DQcsg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JTZIx8_N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2ADzE2c4KueoQpWGFP2DQcsg.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
undefined&lt;/p&gt;

&lt;p&gt;In fact, when running this on a more powerful machine, we can take a memory snapshot and try and see what is going on. For reference, I used a 59.9 MB file that I loaded into memory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JHLvDAIo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AaFxnJ_BIf3OMdqs0X38dcg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JHLvDAIo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AaFxnJ_BIf3OMdqs0X38dcg.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
undefined&lt;/p&gt;

&lt;p&gt;Here, we see that the resulting JS Heap size is nearly triple the actual file size. But in reality, we don’t need to store the whole array in memory, parse it for location data points, and then feed those points into a function that displays them on a map. We could just do this all on the fly…however, that is easier said than done.&lt;/p&gt;
&lt;h3&gt;
  
  
  Chunking &amp;amp; Oboe:
&lt;/h3&gt;

&lt;p&gt;The first solution I thought of was to try and split the file into more manageable chunks of 512 kilobytes at a time. However, this has some inherent flaws with it, mainly that the file that I am trying to load in contains a large “string” that has the format of a JSON object (but isn’t an object yet). Thus when I decide to split and process the file in sequential pieces that are 512 KB long, I can easily run into a situation where I cut an “object” in half.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Eh925VQi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A7u3tcBZJbOa4ofZxNk77Kw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Eh925VQi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A7u3tcBZJbOa4ofZxNk77Kw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So now I needed a way to keep track of half completed objects/objects that were cut off and prepend/append them to the following chunks accordingly to make sure that everything would parse correctly. Though the Google LocationHistory.json file is relatively uniform, the way that chunks can be split is not. Luckily there is an existing library to help take care of all the edge cases that can arise. Enter Oboe.js.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r4PHurfp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2As4QCcGJ5AgN7WSIkDLmwzw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r4PHurfp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2As4QCcGJ5AgN7WSIkDLmwzw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oboe.js is built for dealing with JSON coming from a streaming source. Additionally, it can load JSON trees larger than the available memory on the client as it only processes one JSON node at a time and then drops the node from the memory tree. However, I do not have a streaming source of data. Luckily, after looking around the Oboe codebase for a bit, I found that oboe can be instantiated and passed data through an emit event.&lt;/p&gt;

&lt;p&gt;The oboe code itself is relatively easy to set up. The JSON file we are looking at has the general form of the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;   
  &lt;/span&gt;&lt;span class="nl"&gt;"locations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"timeStampMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"latitudeE7"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"longitudeE7"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"accuracy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;  
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"timeStampMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"latitudeE7"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"longitudeE7"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"accuracy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;  
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;  
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;  
  &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Per Oboe documentation, the &lt;code&gt;locations&lt;/code&gt; node should be targeted and any sub object of that will be passed into the callback function as shown in the code sample below.&lt;/p&gt;

&lt;p&gt;undefined&lt;br&gt;
undefined&lt;/p&gt;

&lt;p&gt;Next, we need to figure out a way to pass in chunks to this function. The chunking function itself is a little bit more complicated, but the main functionality is to process the file in 512 KB portions at a time. The function takes in the file itself (from an input) and the instance of oboe.js (in our case the &lt;code&gt;os&lt;/code&gt; variable).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Note on line 11 the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;oboeInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That line contains the crux of the processing by oboe. The chunk will be sent to our oboe instance in the &lt;code&gt;os&lt;/code&gt; variable as a quasi-stream of data.&lt;/p&gt;
&lt;h3&gt;
  
  
  Displaying the Points:
&lt;/h3&gt;

&lt;p&gt;The last thing to take care of is displaying the data. We chose to use leaflet.js because it was fairly simple to set up and it has a much more diverse 3rd party library ecosystem than Google maps (or any other map library out there that I know of).&lt;/p&gt;

&lt;p&gt;Initializing the map on a div with &lt;code&gt;id='mapid'&lt;/code&gt; is fairly straightforward:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;However, displaying over 1 million location data points requires much more than what the base leaflet.js library can handle. Fortunately, many open source solutions utilize Hierarchical greedy clustering to cluster points at low zoom levels and de-cluster them as the zoom level increases. Vladimir Agafonkin from Mapbox wrote an excellent blog on the algorithmic side of how this process works, and I would highly encourage you to &lt;a href="https://www.mapbox.com/blog/supercluster/"&gt;check it out&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--47xqOT7Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AaR5oEw6Kw8h7-tl48OAmOA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--47xqOT7Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AaR5oEw6Kw8h7-tl48OAmOA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An existing implementation of marker clustering for leaflet exists already with the &lt;a href="https://github.com/SINTEF-9012/PruneCluster"&gt;PruneCluster library&lt;/a&gt;. This library sets itself apart from the others because it has no real upper limit to how many points it can process (it is only constrained by the computing power of the client). The resulting render and update times are amazing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lN5YzTbs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AeX2jgZL6dF-KgrUQeG0_fA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lN5YzTbs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AeX2jgZL6dF-KgrUQeG0_fA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Going back to our oboe.js instance code, we can edit it slightly to account for the PruneCluster library addition:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Results:
&lt;/h3&gt;

&lt;p&gt;After making all the changes above, I was finally able to do some basic tests to see if whether all these optimizations would be worth it. Below are the results (at each file size five trials were done and the time is the average).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--78bY2N60--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A9ouk2MFIkmCGVW8n9PdhdA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--78bY2N60--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A9ouk2MFIkmCGVW8n9PdhdA.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
undefined&lt;/p&gt;

&lt;p&gt;The results were stunning. Although loading the file straight into memory was faster for smaller files, the chunking with oboe stream paid off in the end and gave a nearly linear correlation between load time and file size! In the end, we attached a loading bar to the analyzer to give the user a sense of progress and attached some load time statistics to it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--awwcl8wG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2Avb6Dlz81oBQ628Oo1iXdsQ.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--awwcl8wG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2Avb6Dlz81oBQ628Oo1iXdsQ.gif" alt=""&gt;&lt;/a&gt;&lt;br&gt;
undefined&lt;/p&gt;

&lt;p&gt;And there you have it. Parsing of Google Location History on the frontend. No server needed. In fact, I am hosting the website on github page right now at &lt;a href="http://hack4impact.github.io/cls"&gt;&lt;strong&gt;hack4impact.github.io/cls&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Overall, this project was a huge success. During the semester, I interacted with some of the incredible people at Community Legal Services to create this product which will aid many legal workers for years to come. I would highly encourage those who know how to program to volunteer their skills to assist community organizations better achieve their mission. It is an incredibly rewarding experience for both parties and will challenge you to apply your skills towards creating sustainable and functional products.&lt;/p&gt;

&lt;p&gt;You can find the source code for the project at &lt;a href="http://github.com/hack4impact/cls"&gt;our repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My full team is: Product Manager: &lt;a href="https://medium.com/u/925c61658d3f"&gt;Krishna Bharathala&lt;/a&gt;, Team Members: &lt;a href="https://medium.com/u/88e474228778"&gt;Katie Jiang&lt;/a&gt;, &lt;a href="https://medium.com/u/5c1ca78001e6"&gt;Daniel Zhang&lt;/a&gt;, &lt;a href="https://medium.com/u/2ce68e1ad424"&gt;Santi Buenahora&lt;/a&gt;, and &lt;a href="https://medium.com/u/e89dd252d52"&gt;Rachel H&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By &lt;a href="https://medium.com/@abhisuri97"&gt;Abhinav Suri&lt;/a&gt; on &lt;a href="https://medium.com/p/dedca8380ce3"&gt;March 20, 2017&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>NextJS, Contentful CMS, GraphQL, oh my!</title>
      <dc:creator>Ben Holmes</dc:creator>
      <pubDate>Wed, 04 Nov 2020 22:42:55 +0000</pubDate>
      <link>https://dev.to/hack4impact/nextjs-contentful-cms-graphql-oh-my-352o</link>
      <guid>https://dev.to/hack4impact/nextjs-contentful-cms-graphql-oh-my-352o</guid>
      <description>&lt;p&gt;We built the new &lt;a href="https://hack4impact.org" rel="noopener noreferrer"&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't help you answer &lt;em&gt;what tools should I use to build our site'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's GraphQL endpoints (yes, they'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="https://nextjs.org" rel="noopener noreferrer"&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'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="https://medium.com/walmartglobaltech/the-benefits-of-server-side-rendering-over-client-side-rendering-5d07ff2cefe8" rel="noopener noreferrer"&gt;serverside rendering&lt;/a&gt;). This is pretty promising as &lt;a href="https://hack4impact.org" rel="noopener noreferrer"&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="https://www.contentful.com" rel="noopener noreferrer"&gt;&lt;strong&gt;Contentful&lt;/strong&gt;&lt;/a&gt; is one of the more popular "headless CMSs" right now, and it'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 "structured content" in particular which is great for static sites like ours! Still, if you're looking for a simplified, key-value store for your copy, there are some &lt;a href="https://www.sanity.io" rel="noopener noreferrer"&gt;shiny alternatives&lt;/a&gt; to consider.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://graphql.org" rel="noopener noreferrer"&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 "shape" of the content you want (with necessary filtering and sorting), and the CMS responds with the associated values. We'll dive into some code samples soon, but it'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's roughly 10 billion ways to build a static site these days (citation needed), with another &lt;a href="https://www.netlify.com/blog/" rel="noopener noreferrer"&gt;10 billion blog posts&lt;/a&gt; on how to tackle the problem. So don'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's open up Contentful first. If you're 100% new to the platform, Contentful documents a lot of &lt;a href="https://www.contentful.com/developers/docs/concepts/" rel="noopener noreferrer"&gt;core concepts over here&lt;/a&gt; to get up to speed on "entries" and "content models."&lt;/p&gt;

&lt;p&gt;When you're feeling comfortable, whip up a new workspace and create a new content model of your choosing. We'll use our "Executive Board Member" model as an example here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fcontentful-model.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fcontentful-model.jpg" alt="Contentful model example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you've saved this model, go and make some content entries in the "Content" panel. We'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;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fcontentful-entries.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fcontentful-entries.jpg" alt="Contentful entry example, filtered by "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before moving on, let's get some API keys for our website to use. Just head to "Settings &amp;gt; API keys" and choose "Add API key" 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'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's super easy to make a NextJS project from scratch using their &lt;code&gt;npx&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app dope-contentful-example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="https://classic.yarnpkg.com/en/" rel="noopener noreferrer"&gt;Yarn&lt;/a&gt;. By default, Next will set up your project with Yarn if you have it installed globally. &lt;a href="https://www.youtube.com/watch?v=5cDLZqe735k" rel="noopener noreferrer"&gt;It's your prerogative&lt;/a&gt; though!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You may have found a "NextJS + Contentful" example in the Next docs as well. &lt;strong&gt;Don't install that one!&lt;/strong&gt; We'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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NEXT_PUBLIC_CONTENTFUL_SPACE_ID&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;Your Space ID from Contentful]
&lt;span class="nv"&gt;NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;Your Content Delivery API access token from Contentful]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just fill these in with your API keys and you'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's a little verbose, but it allows Next to pick up your keys without the hassle of setting up, say, &lt;a href="https://github.com/motdotla/dotenv" rel="noopener noreferrer"&gt;dotenv&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fetching some GraphQL data
&lt;/h2&gt;

&lt;p&gt;Alright, so we've set the stage. &lt;em&gt;Now let's fetch our data!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We'll be using GraphiQL to view our "schemas" with a nice GUI. &lt;a href="https://www.electronjs.org/apps/graphiql" rel="noopener noreferrer"&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="https://docs.microsoft.com/en-us/windows/wsl/install-win10" rel="noopener noreferrer"&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;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fgraphiql-welcome.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fgraphiql-welcome.jpg" alt="GraphiQL welcome screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let'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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;https://graphql.contentful.com/content/v1/spaces/[Space ID]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you try to hit the play button ▶️ after this step, you should get an authorizaton error. That's because we haven'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;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fgraphiql-headers.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fgraphiql-headers.jpg" alt="Edit HTTP headers screen. Make sure Header name = " value="Bearer [Contentful access token]"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After saving, you should see some info appear in your "Documentation Explorer." If you click on the &lt;strong&gt;query: Query&lt;/strong&gt; link, you'll see an overview of all your Content models from Contentful.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fgraphiql-docs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fgraphiql-docs.jpg" alt="Schema explorer overview"&gt;&lt;/a&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'll notice there's a distinction between individual entries and a "collection" (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'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'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'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's jump into an example. First, let's just get the list of names and emails for all "Executive Board Member" entries. If you'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're looking for multiple entries, we'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're free to retrieve: &lt;strong&gt;total, skip, limit, and items.&lt;/strong&gt; You'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're hoping to retrieve. Let'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;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fgraphiql-exec-member-entry.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fgraphiql-exec-member-entry.jpg" alt="Schema for our Executive Board Member content model"&gt;&lt;/a&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'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's the basic skeleton of info we need to retrieve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;executiveBoardMemberCollection -&amp;gt; query for a collection of entries
  items -&amp;gt; retrieve the list items
    name -&amp;gt; retrieve the name for each list item
    email -&amp;gt; and the email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can convert this skeleton to the JSON-y syntax GraphQL expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;executiveBoardMemberCollection&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;
      &lt;span class="nx"&gt;email&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... and enter this into GraphiQL's textbox and hit play ▶️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fgraphiql-response.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fgraphiql-response.jpg" alt="Entering our query into GraphiQL, with response data appearing on the right"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Boom! There'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'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;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fgraphiql-filter.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fgraphiql-filter.jpg" alt="Applying a filter using "&gt;&lt;/a&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'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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;executiveBoardMemberCollection(where: {linkedIn_exists: true}) { ... }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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're off to the races 🏎&lt;/p&gt;

&lt;p&gt;Let'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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/about&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStaticProps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// our beautiful Contentful content&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'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's &lt;code&gt;props&lt;/code&gt; at runtime. This means you &lt;em&gt;don'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's a distinction between getting these props "on every page request" versus retrieval "at build time." For a full rundown of the difference, &lt;a href="https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering" rel="noopener noreferrer"&gt;check out the NextJS docs&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Inside this function, we'll make a simple call to Contentful using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" rel="noopener noreferrer"&gt;fetch&lt;/a&gt; (but feel free to use axios if that's more your speed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStaticProps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// first, grab our Contentful keys from the .env file&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;space&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_CONTENTFUL_SPACE_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// then, send a request to Contentful (using the same URL from GraphiQL)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`https://graphql.contentful.com/content/v1/spaces/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// GraphQL *always* uses POST requests!&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// add our access token header&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="c1"&gt;// send the query we wrote in GraphiQL as a string&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="c1"&gt;// all requests start with "query: ", so we'll stringify that for convenience&lt;/span&gt;
          &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
          {
            executiveBoardMemberCollection {
              items {
                name
                email
              }
            }
          }
                `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// grab the data from our response&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Woah, that's a lot going on! In the end, we'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're sending a &lt;code&gt;body&lt;/code&gt; field to Contentful, containing the "shape" 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;{ "query": "string" }&lt;/code&gt;.&lt;/strong&gt; To make this easier to type, we create a JavaScript object starting with "query" 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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStaticProps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;execBoardMembers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executiveBoardMemberCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and do something with those props in our page component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AboutPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;execBoardMembers&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;execBoardMembers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;execBoardMember&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"exec-member-profile"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;execBoardMember&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;execBoardMember&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Hopefully, you'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's why we wrote a little helper function on our project to streamline the process.&lt;/p&gt;

&lt;p&gt;In short, we're gonna move all our API call logic into a utility function, accepting the actual "body" of our query as a parameter. Here's how that could look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// utils/contentful&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;space&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_CONTENTFUL_SPACE_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// add a try / catch loop for nicer error handling&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`https://graphql.contentful.com/content/v1/spaces/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="c1"&gt;// throw our query (a string) into the body directly&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// add a descriptive error message first,&lt;/span&gt;
    &lt;span class="c1"&gt;// so we know which GraphQL query caused the issue&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`There was a problem retrieving entries with the query &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fetchContent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@utils/contentful&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStaticProps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
        {
            executiveBoardMemberCollection {
                items {
                name
                email
            }
          }
      }
  `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;execBoardMembers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executiveBoardMemberCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and we're ready to make Contentful queries across the site ✨&lt;/p&gt;

&lt;h3&gt;
  
  
  Aside: use the "@" 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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"baseUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@utils/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"utils/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@components/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"components/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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'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;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fgraphiql-rich-text.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHolben888%2Fpersonal-blog%2Fmain%2Fnextjs-contentful%2Fgraphiql-rich-text.jpg" alt="GraphiQL response when querying for rich text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...which isn't super useful on its own. To convert this into some nice HTML, you'll need a &lt;a href="https://www.npmjs.com/package/@contentful/rich-text-html-renderer" rel="noopener noreferrer"&gt;special package from Contentful&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;--save-dev&lt;/span&gt; @contentful/rich-text-html-renderer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will take in the JSON object and (safely) render HTML for your component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;documentToHtmlString&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@contentful/rich-text-html-renderer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AboutPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;execBoardMember&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
    &lt;span class="na"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;documentToHtmlString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;execBoardMember&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, using &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt; is pretty tedious. We'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'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="https://codesandbox.io/s/hack4impact-website-0kn5m?file=/pages/about/index.tsx" rel="noopener noreferrer"&gt;Head over here&lt;/a&gt;&lt;/strong&gt; to see how we retrieve our executive board members on &lt;a href="https://hack4impact.org/about" rel="noopener noreferrer"&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="https://github.com/hack4impact/hack4impact-website" rel="noopener noreferrer"&gt;Our repo&lt;/a&gt; is 100% open as well, so give it a ⭐️ if this article helped you!&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn a little something?
&lt;/h2&gt;

&lt;p&gt;Awesome. In case you missed it, I launched an &lt;a href="https://tinyletter.com/bholmesdev" rel="noopener noreferrer"&gt;my "web wizardry" newsletter&lt;/a&gt; to explore more knowledge nuggets like this!&lt;/p&gt;

&lt;p&gt;This thing tackles the &lt;a href="https://www.swyx.io/first-principles-approach/" rel="noopener noreferrer"&gt;"first principles"&lt;/a&gt; of web development. In other words, what are all the janky browser APIs, bent CSS rules, and semi-accessible HTML that make all our web projects tick? If you're looking to go &lt;em&gt;beyond the framework&lt;/em&gt;, this one's for you dear web sorcerer 🔮&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tinyletter.com/bholmesdev" rel="noopener noreferrer"&gt;Subscribe away right here&lt;/a&gt;. I promise to always teach and never spam ❤️&lt;/p&gt;

</description>
      <category>react</category>
      <category>graphql</category>
      <category>webdev</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Building a sexy, mobile-ready navbar in any web framework</title>
      <dc:creator>Ben Holmes</dc:creator>
      <pubDate>Tue, 06 Oct 2020 22:44:09 +0000</pubDate>
      <link>https://dev.to/hack4impact/building-a-sexy-mobile-ready-navbar-in-any-web-framework-3lm2</link>
      <guid>https://dev.to/hack4impact/building-a-sexy-mobile-ready-navbar-in-any-web-framework-3lm2</guid>
      <description>&lt;p&gt;I'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="https://reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt;, &lt;a href="https://svelte.dev/" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt;, and plain-ole JS, and I think I'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's the end goal?
&lt;/h2&gt;

&lt;p&gt;Here's a screen-grab from my most recent project: redesigning the &lt;a href="https://hack4impact.org" rel="noopener noreferrer"&gt;Hack4Impact&lt;/a&gt; nonprofit site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9zokfxj7g5lsp6248d8m.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9zokfxj7g5lsp6248d8m.gif" alt="Demoing responsive mobile nav-bar with screen resize"&gt;&lt;/a&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 "formula" we're after!&lt;/p&gt;

&lt;h2&gt;
  
  
  Lay down some HTML
&lt;/h2&gt;

&lt;p&gt;Let's define the general structure of our navbar first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;nav&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"logo"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"dope-logo.svg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Our professional logo (ideally an svg!)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mobile-dropdown-toggle"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Cool hamburger icon --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dropdown-link-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/about"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;About Us&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/work"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Our Work&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    ...
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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'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's certainly a valid one! However, &lt;a href="https://css-tricks.com/wrapup-of-navigation-in-lists/" rel="noopener noreferrer"&gt;this nuanced for / against piece from Chris Coyier&lt;/a&gt; really solidified things for me. In short: lists aren'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="https://dev.to/bholmesdev/comment/16hi9"&gt;I clarified the issue to a kind commenter here!&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;You'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't do anything fancy for desktop users. But once we hit our mobile breakpoint, we'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're slapping an &lt;code&gt;aria-hidden&lt;/code&gt; attribute on our dropdown toggle. For a simple nav like this, there's no reason for a screenreader to pick up on this button; it can always pick up on all our links even when they're "visually hidden", so there's no toggling going on 🤷‍♀️ Still, if you really want to mimic the "toggle" effect for these users (which you should for super busy navbars), you can &lt;a href="https://www.w3.org/WAI/GL/wiki/Using_aria-expanded_to_indicate_the_state_of_a_collapsible_element" rel="noopener noreferrer"&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;iframe height="600" src="https://codepen.io/bholmesdev/embed/zYqQvxj?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Now, some CSS
&lt;/h2&gt;

&lt;p&gt;Before worrying about all that mobile functionality, let'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'll set up the alignment and width for our navbar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;nav&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1200px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* should match the width of your website content */&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* center each of our links vertically */&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* center all our content horizontally when we exceed that max-width */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.logo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* push all our links to the right side, leaving the logo on the left */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.dropdown-link-container&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* space out all our links */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.mobile-dropdown-toggle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* hide our hamburger button until we're on mobile */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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's a little before-and-after to show you what I mean.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flau0lemuw8j7qoih7qvr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flau0lemuw8j7qoih7qvr.gif" alt="Nav bar with some bad resizing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Before: *&lt;/em&gt; Our nav elements stick to the edges of the screen. This doesn't match up with our page content very well, and makes navigation awkward on larger devices.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fq3fh0jg2lnz4eph7tzf4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fq3fh0jg2lnz4eph7tzf4.gif" alt="Nav bar with some good resizing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;After: *&lt;/em&gt; Everything is &lt;em&gt;beautifully&lt;/em&gt; aligned, making our website a lot more "scan-able."&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're 90% done already! Here's another pen to see it in action:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/bholmesdev/embed/eYZapdM?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding the dropdown
&lt;/h3&gt;

&lt;p&gt;Alright, now let's tackle our dropdown experience. First, we'll just focus on re-styling our links into a vertical column that takes up the height of the page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;768px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;/* arbitrary breakpoint, around the size of a tablet */&lt;/span&gt;
  &lt;span class="nc"&gt;.dropdown-link-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/* first, make our dropdown cover the screen */&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c"&gt;/* fix nav height on mobile safari, where 100vh is a little off */&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;-webkit-fill-available&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c"&gt;/* then, arrange our links top to bottom */&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c"&gt;/* center links vertically, push to the right horizontally.
       this means our links will line up with the rightward hamburger button */&lt;/span&gt;
    &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex-end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c"&gt;/* add margins and padding to taste */&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7vw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7vw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;lightblue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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'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'm sure you're thinking &lt;em&gt;"what, how is 100vh not 100% of the user's screen size? What did Apple do this time?"&lt;/em&gt; Well, the problem comes from the iOS' disappearing URL bar. When you scroll, a bunch of UI elements slide out of the way to give you more screen real estate. That'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="https://bitsofgood.org" rel="noopener noreferrer"&gt;Bits of Good nonprofit homepage&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8v5kv4luayysgt93wrv1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8v5kv4luayysgt93wrv1.gif" alt="Safari nav height problem"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the links aren'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'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'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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;768px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.logo&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.mobile-dropdown-toggle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.mobile-dropdown-toggle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* override that display: none attribute from before */&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.dropdown-link-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;...&lt;/span&gt;
    &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* we'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 class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Squoosh this CodePen to our breakpoint size to see these styles at work:&lt;br&gt;
&lt;iframe height="600" src="https://codepen.io/bholmesdev/embed/KKzLdbR?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's animate that dropdown
&lt;/h2&gt;

&lt;p&gt;Alright, we have most of our markup and styles finished up. Now, let's make that hamburger button do something!&lt;/p&gt;

&lt;p&gt;We'll start with handling the menu button clicks. To show you how simple this setup is, I'll just use vanilla JS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// get a ref to our navbar (assuming it has this id)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;navElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main-nav&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mobile-dropdown-toggle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// when we click our button, toggle a CSS class!&lt;/span&gt;
    &lt;span class="nx"&gt;navElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dropdown-opened&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we'll animate our dropdown into view whenever that &lt;code&gt;dropdown-opened&lt;/code&gt; class gets applied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* inside the same media query from before */&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;768px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="nc"&gt;.dropdown-link-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;...&lt;/span&gt;
    &lt;span class="c"&gt;/* our initial state */&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* fade out */&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* move out of view */&lt;/span&gt;
    &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="m"&gt;0.2s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opacity&lt;/span&gt; &lt;span class="m"&gt;0.2s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* transition these smoothly */&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nt"&gt;nav&lt;/span&gt;&lt;span class="nc"&gt;.dropdown-opened&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;.dropdown-link-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* fade in */&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* move into view */&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;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;iframe height="600" src="https://codepen.io/bholmesdev/embed/jOqoQRZ?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&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't be too difficult! You can &lt;strong&gt;keep all the CSS the same,&lt;/strong&gt; but here's a component snippet you can plop into React:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BigBoyNav&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mobileNavOpened&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMobileNavOpened&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toggleMobileNav&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setMobileNavOpened&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;mobileNavOpened&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;nav&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;mobileNavOpened&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dropdown-opened&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      ...
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mobile-dropdown-toggle"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;toggleMobileNav&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;aria-hidden&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And one for Svelte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ...might've included this to show how simple Svelte is :) --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mobileNavOpened&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toggleMobileNav&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;mobileNavOpened&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;mobileNavOpened&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;className:mobileNavOpened=&lt;/span&gt;&lt;span class="s"&gt;"dropdown-opened"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ...
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mobile-dropdown-toggle"&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;{toggleMobileNav}&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...You get the point. It's a toggle 😆&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'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's no need to hide the dropdown!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If we'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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// if we clicked on something inside our dropdown...&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ourDropdownElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;navElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dropdown-opened&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...but this wouldn't be super accessible 😓. Sure, it handles mouse clicks, but how will it fare against keyboard navigation with the "tab" key? In that case, the user will tab to the link they want, hit "enter," and stay stuck in &lt;code&gt;dropdown-opened&lt;/code&gt; without any feedback!&lt;/p&gt;

&lt;p&gt;Luckily, there's a more "declarative" 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'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's see how &lt;a href="http://nextjs.org" rel="noopener noreferrer"&gt;NextJS&lt;/a&gt; handles this problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BigBoyNav&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// grab the current route with a React hook&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="c1"&gt;// whenever "activeRoute" changes, hide our dropdown&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setMobileNavOpened&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;activeRoute&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vanilla &lt;a href="https://reactrouter.com" rel="noopener noreferrer"&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 👍&lt;/p&gt;

&lt;h3&gt;
  
  
  Handle the "escape" 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 "escape" key is pressed. This is bound to a very specific user interaction, so we're free to add an event listener for this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vanilla JS&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;escapeKeyListener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KeyboardEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Escape&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;navElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dropdown-opened&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keypress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;escapeKeyListener&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and for component frameworks, make sure you remove that event listener whenever the component is destroyed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// React&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;escapeKeyListener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KeyboardEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Escape&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;setMobileNavOpened&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// add the listener "on mount"&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keypress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;escapeKeyListener&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// remove the listener "on destroy"&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keypress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;escapeKeyListener&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  See a fully-functional React example 🚀
&lt;/h2&gt;

&lt;p&gt;If you're curious how this could all fit together in a React app, our entire &lt;a href="https://hack4impact.org" rel="noopener noreferrer"&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="https://codesandbox.io/s/hack4impact-website-0kn5m?file=/components/shared/Nav/index.tsx" rel="noopener noreferrer"&gt;head over here&lt;/a&gt;.&lt;/strong&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Learn a little something?
&lt;/h2&gt;

&lt;p&gt;Awesome. In case you missed it, I launched an &lt;a href="https://tinyletter.com/bholmesdev" rel="noopener noreferrer"&gt;my "web wizardry" newsletter&lt;/a&gt; to explore more knowledge nuggets like this!&lt;/p&gt;

&lt;p&gt;This thing tackles the &lt;a href="https://www.swyx.io/first-principles-approach/" rel="noopener noreferrer"&gt;"first principles"&lt;/a&gt; of web development. In other words, what are all the janky browser APIs, bent CSS rules, and semi-accessible HTML that make all our web projects tick? If you're looking to go &lt;em&gt;beyond the framework&lt;/em&gt;, this one's for you dear web sorcerer 🔮&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tinyletter.com/bholmesdev" rel="noopener noreferrer"&gt;Subscribe away right here&lt;/a&gt;. I promise to always teach and never spam ❤️&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>react</category>
      <category>svelte</category>
    </item>
  </channel>
</rss>
