<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Monica&apos;s Digital Playground</title><description>Monica Powell is a Senior Software Engineer who crafts innovative educational software for classrooms.</description><link>https://aboutmonica.com/</link><item><title>2019 into 2020, Year in Review</title><link>https://aboutmonica.com/blog/2019-year-in-review/</link><guid isPermaLink="true">https://aboutmonica.com/blog/2019-year-in-review/</guid><description>I could go on and on about the last 10 years but it&apos;s officially 2020 and I&apos;ve decided to start by writing my first public year in review!</description><pubDate>Wed, 01 Jan 2020 02:43:13 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/media/monica-reactconf2.jpeg#center&quot; alt=&quot;Monica speaking at React Conf 2019&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The past decade included many transitions for me, from graduating high school to graduating from my dream college in New York City to successfully switching careers and becoming a software developer.&lt;/p&gt;
&lt;p&gt;&amp;lt;Tweet tweetLink=&quot;waterproofheart/status/1201359059791822851?s&quot;/&amp;gt;&lt;/p&gt;
&lt;p&gt;I’m hoping in the next decade that I can continue to do meaningful work to make it easier for underrepresented folks to thrive in tech roles, learning how to work smarter not harder, further develop my sense of self &amp;amp; identity outside of my career/professional skills, and tackle some of my vices for the long run (holding onto things too long and procrastinating esp. when that means more work or trouble for me later, etc.).&lt;/p&gt;
&lt;p&gt;I could go on and on about the last 10 years but it&apos;s officially 2020 and I&apos;ve decided to start by writing my first public &lt;em&gt;year&lt;/em&gt; in review!&lt;/p&gt;
&lt;p&gt;In 2019, I started to learn new technologies including TypeScript, Concourse, GraphQL, etc. and explored new-to-me areas of the codebase at my full-time job. At work, I presented my product engineering work at various internal demo days, had the opportunity to work with more legacy code and also contribute to production-level software from its inception. In 2018, I primarily contributed to a relatively new (but pre-existing) software project. Whereas, in 2019 I had the chance to create a deployment pipeline, streamline the creation of new packages via CLI prompts (which inspired &lt;a href=&quot;https://noti.st/monica/AjCX04/automate-react-workflow&quot;&gt;my talk on generating React components&lt;/a&gt; at React Girls Conf in London) and lead more independent projects and investigations.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/automate-react-workflow-sketchnotes.jpg#center&quot; alt=&quot;Sketchnotes from Monica&apos;s Automating React Workflow Talk at React Girls Conf in London&quot; /&gt;
&lt;em&gt;Sketchnotes by [@malweene](&amp;lt;Tweet tweetLink=&quot;malweene) from my talk at React Girls Conf&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Below are some professional-related highlights of my year:&lt;/p&gt;
&lt;h2&gt;Redesigning My Site&lt;/h2&gt;
&lt;p&gt;I&apos;ve started to consider my site my playground (similar to a &lt;a href=&quot;https://joelhooks.com/digital-garden&quot;&gt;digital garden&lt;/a&gt; worthy of a &lt;a href=&quot;https://vanschneider.com/a-love-letter-to-personal-websites&quot;&gt;love letter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I decided to completely re-do my website in September and am pretty happy with where it landed. Instead of managing a separate blog and portfolio site I combined the two which has given me the flexibility to experiment with different website content hierarchies. The site was written with &lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;GatsbyJS&lt;/a&gt; and through building my site I learned more about writing JavaScript in a server-side rendered site and how to use &lt;a href=&quot;https://graphql.org/&quot;&gt;GraphQL&lt;/a&gt; in Gatsby. The previous version of my site was using outdated technology and in my opinion, was difficult to update as it wasn&apos;t modularized.&lt;/p&gt;
&lt;p&gt;&amp;lt;Tweet tweetLink=&quot;waterproofheart/status/1178488285460680704?s&quot;/&amp;gt;&lt;/p&gt;
&lt;p&gt;Throughout the year I had a desire to be more creative and &lt;a href=&quot;https://www.swyx.io/writing/learn-in-public/&quot;&gt;learn in public&lt;/a&gt; more and so far my site has allowed me a creative outlet to explore and provided me opportunity to share some of my learnings from my explorations like &lt;a href=&quot;https://www.aboutmonica.com/blog/create-gatsby-blog-search-tutorial&quot;&gt;How To Add Search Functionality to a Gatsby Blog&lt;/a&gt; and &lt;a href=&quot;https://www.aboutmonica.com/blog/less-javascript-is-more&quot;&gt;Less JavaScript Makes Font Awesome More Awesome&lt;/a&gt;. While building out my site with GraphQL I also started using GraphQL more at work as both an API consumer and developer. I learned more tech-wise throughout the year both at work and beyond I want to push myself to explore new creative mediums in the future as that is something I wish I had done more of.&lt;/p&gt;
&lt;h2&gt;More Public Speaking and Community Involvement&lt;/h2&gt;
&lt;p&gt;After attending &lt;a href=&quot;https://www.globaldiversitycfpday.com/&quot;&gt;Global Diversity CFP Day&lt;/a&gt; in March, I leaned into more public speaking and even spoke internationally. I spoke at a Women Who Code NYC, useReact NYC, React Girls Conference, React Conf, Barnard College (2x), Columbia University and The Flatiron School. I also made my podcast debut and was featured on &lt;a href=&quot;https://egghead.io/podcasts/personal-growth-from-open-source-and-meetups-with-monica-powell&quot;&gt;an episode of the Egghead.io podcast&lt;/a&gt; where I discussed my journey to landing my first engineering role, attending and organizing meetups and contributing to open-source.&lt;/p&gt;
&lt;p&gt;At the end of 2018, I was accepted into &lt;a href=&quot;https://www.devcolor.org/&quot;&gt;Dev/Color&lt;/a&gt; and as I&apos;m writing this I just completed my first year. Being a part of Dev/Color and having the opportunity to connect with other Black software engineers has helped me grow in terms of developing professional development goals as an engineer. Before I transitioned into engineering full-time a lot of my professional goals were tied to just getting in the door as an engineer and after I started working as an engineer I needed to start shifting my goals in the context of &quot;okay, I&apos;m an engineering individual contributor now what?&quot;.&lt;/p&gt;
&lt;p&gt;In 2017, I went to &lt;a href=&quot;https://elaconf.github.io/&quot;&gt;Ela Conf&lt;/a&gt; where I met people who changed my life (Michelle, Lauren, Laura, Kristen)! Ela Conf was an inclusive tech conference and community for cis women, trans men and trans women, and gender-queer people. I wrote about &lt;a href=&quot;https://code.likeagirl.io/what-i-learnt-from-attending-a-gender-inclusive-tech-conference-f222f7031512&quot;&gt;what I learned from attending Ela Conf&lt;/a&gt; which was centered around self-care, inclusion, diversity, learning, and growth.&lt;/p&gt;
&lt;p&gt;To this day I continue to be involved with a ~ goal ~ group that spun out of Ela Conf and act as a facilitator. We have weekly check-ins and monthly video calls which have provided a consistent time for me to check in with myself and others about what we&apos;d like to accomplish. When I first joined this group I was not yet working as a software engineer and it has been inspiring to see over the last 2+ years how my goals and accomplishments along with everyone else in that group have evolved.&lt;/p&gt;
&lt;p&gt;In addition to joining other communities, I hosted 7 React Meetup events for the React Ladies community which I founded in 2018. I am looking forward to hosting more events in 2020 and finding co-organizers. Our next scheduled event is Global Diversity CFP Day in January. If you’re interested in being a co-organizer of React Ladies e-mail me at monica[at]aboutmonica.com. One of my highlights of React Ladies from 2019 was the Hacktoberfest we hosted with StackOverflow which helped make contributing to open-source feel less scary to some people.&lt;/p&gt;
&lt;p&gt;&amp;lt;Tweet tweetLink=&quot;CourtneyPure/status/1179215976505778176?ref_src=twsrc%5Etfw&quot;/&amp;gt;&lt;/p&gt;
&lt;p&gt;Another highlight was seeing the community rally together to provide Sylwia Vargas, a React Ladies member a free ticket to a Kent C. Dodds workshop, thank you Donavon West and Christian Nwamba!&lt;/p&gt;
&lt;p&gt;&amp;lt;Tweet tweetLink=&quot;SylwiaVargas/status/1153751781324808193?ref_src=twsrc%5Etfw&quot;/&amp;gt;&lt;/p&gt;
&lt;p&gt;In the larger React community, I was featured in a zine that was distributed at React Conf alongside some amazing women involved in the React community. Thanks to Rachel Nabors for putting this project together and for including me.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/react-ladies-zine-monica-powell.png#center&quot; alt=&quot;Photo of React Ladies Zine Interview with Monica Powell&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Overall, I am excited about what I accomplished in 2019 and for what&apos;s to come in 2020. I want to focus on helping other underrepresented folks in tech thrive, dive deeper into API development (REST + GraphQL), build a stronger understanding of Computer Science (data structures and algorithms), learn in public more and continue public speaking. In terms of non-engineering related goals, I want to discover new hobbies(and &lt;a href=&quot;https://lynnandtonic.com/thoughts/entries/why-do-work-without-a-practical-purpose/&quot;&gt;do more things without a practical purpose&lt;/a&gt;), explore different creative outlets, digital and analog illustration and expand on the fitness routine I doubled-down on last year.&lt;/p&gt;
&lt;p&gt;I hope 2020 gives us all a fresh start.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Automating File Creation With JavaScript</title><link>https://aboutmonica.com/blog/2020-01-29-automating-file-creation-with-javascript/</link><guid isPermaLink="true">https://aboutmonica.com/blog/2020-01-29-automating-file-creation-with-javascript/</guid><description>Guide to using Plop a micro-generator to generate new text-based files.</description><pubDate>Wed, 29 Jan 2020 12:40:44 GMT</pubDate><content:encoded>&lt;p&gt;Do you ever find yourself copying and pasting the same boilerplate code for multiple files at a time? Do you stop and think every time you have to construct an &lt;a href=&quot;https://www.iso.org/iso-8601-date-and-time-format.html&quot;&gt;ISO 8601&lt;/a&gt; formatted date? 🤔
How frustrating would it be if you could no longer copy and paste your template code from one file to another?&lt;/p&gt;
&lt;p&gt;This article will walk through how to quickly create a command-line interface (CLI) tool that generates text-based files. In particular, the examples in this article will walk through how to create templates to generate a new &lt;code&gt;.jsx&lt;/code&gt; Page in a &lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;Gatsby&lt;/a&gt; blog with tests as well as how to generate markdown files with the initial structure of a blog post. These examples should serve as inspiration as the sky is the limit in regards to what type of text files can be generated based on the needs of a particular developer or project.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/plop-recording.gif&quot; alt=&quot;gif of the final CLI created by this tutorial&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Why Scaffolding?&lt;/h2&gt;
&lt;p&gt;The CLI we will be creating is a type of scaffolding software as it generates starter code based on templates. Note that starter code generated by scaffolding generally is not ready for production however it still has benefits as it can improve the developer experience, make it easier to implement standardization and enable faster software delivery.&lt;/p&gt;
&lt;p&gt;Some of the benefits of scaffolding can be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;involving less work - no more copying and pasting boilerplate code (i.e., relative imports from one file to another)&lt;/li&gt;
&lt;li&gt;automating the implementation of design patterns and best practices&lt;/li&gt;
&lt;li&gt;reducing time to generate new projects or components&lt;/li&gt;
&lt;li&gt;being less error-prone than the manual process of copy &amp;amp; pasting &amp;amp; editing&lt;/li&gt;
&lt;li&gt;encouraging consistency and implementation of design patterns&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Scaffolding can answer questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Where should translations for this React component live?&lt;/li&gt;
&lt;li&gt;Where can I find our current code standards?&lt;/li&gt;
&lt;li&gt;What directory should this type of file live in?&lt;/li&gt;
&lt;li&gt;Should I use cameCase? snake_case? kebab-case? PascalCase? UPPERCASE_SNAKE_CASE?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Is scaffolding &quot;worth it&quot;?&lt;/h2&gt;
&lt;p&gt;Implementing scaffolding takes time. The potential benefits of scaffolding a particular piece of software versus time involved in developing it should be assessed to determine if it&apos;s worth the time and effort to implement scaffolding. If we are analyzing the estimated time invested vs. saved and not other intangible benefits like consistency or reducing context switching you can use the &lt;a href=&quot;https://xkcd.com/1205/&quot;&gt;below XKCD comic&lt;/a&gt; to assess if it&apos;s worth implementing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/is_it_worth_the_time.png&quot; alt=&quot;is it worth the time comic&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;xkcd Is It Worth the Time?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In terms of other potential down-sides, code requirements often evolve over time and scaffolded templates may require future maintenance as new requirements surface. Ideally refactoring the scaffolding template should feel like a natural extension of a workflow versus like maintenance is additional overhead and slowing down the process. Some implementation details or decisions can be concealed by scaffolding which can reduce context depending on the tool the actual logic being used to generate files can be easily accessible.&lt;/p&gt;
&lt;h2&gt;Micro-generator tool: PlopJS&lt;/h2&gt;
&lt;p&gt;If you’re looking for a lightweight way to introduce scaffolding into your workflow consider using &lt;a href=&quot;https://github.com/plopjs/plop&quot;&gt;Plop&lt;/a&gt;, a micro-generator. Plop allows developers to generate files based on user input via a Command Line Interface (CLI) with minimal setup.&lt;/p&gt;
&lt;h3&gt;How does Plop work?&lt;/h3&gt;
&lt;p&gt;PlopJS combines the Handlebars templating language and &lt;a href=&quot;https://github.com/SBoudrias/Inquirer.js/&quot;&gt;Inquirer.js&lt;/a&gt;. Inquirer.js is a tool for collecting user input via CLI. You can present questions a.k.a CLI prompts in different formats with inquirer. &lt;a href=&quot;https://handlebarsjs.com/&quot;&gt;Handlebars&lt;/a&gt; is a templating language that you may be familiar with. Templating languages are used in a variety of contexts from displaying React props, creating e-mail templates, or even making your workflow easier as we&apos;ll see today. Before, I was using &lt;code&gt;.jsx&lt;/code&gt; in React I worked with the &lt;a href=&quot;https://shopify.github.io/liquid/&quot;&gt;Liquid&lt;/a&gt; templating language in &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; and Handlebars in &lt;a href=&quot;https://foundation.zurb.com/emails.html&quot;&gt;Foundation for Emails&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;By combining the functionality of Inquirer.js with Handlebars, plop allows users to quickly create templates with minimal setup. If you&apos;re not familiar with software templates you can think of them as similar to mail merge in a word processor. In mail merge, there&apos;s generally a spreadsheet with data which is and then merged with a template document that has placeholder variables. When the data and template are combined with mail merge the result is a version of the document that contains data in the proper places (as determined by the placeholder variables). The data in that file is populated during the mail merge process and customized as appropriate for the recipient. In our case, the data entered in the CLI will be populated into the template and generate a new file when we run &lt;code&gt;plop&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Plop Set Up&lt;/h3&gt;
&lt;p&gt;If you already have a directory with a &lt;code&gt;package.json&lt;/code&gt; where you want to generate files then Plop can be installed with &lt;code&gt;yarn&lt;/code&gt; with the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn add -D plop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or &lt;code&gt;npm&lt;/code&gt; using:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; npm install —save-dev plop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you don&apos;t already have a &lt;code&gt;package.json&lt;/code&gt; you can create one by typing &lt;code&gt;yarn init&lt;/code&gt; or &lt;code&gt;npm init&lt;/code&gt; and walking through the steps and then installing &lt;code&gt;plop&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once &lt;code&gt;plop&lt;/code&gt; is installed as a package dependency we should update the &lt;code&gt;scripts&lt;/code&gt; in the &lt;code&gt;package.json&lt;/code&gt; file to enable us to run &lt;code&gt;yarn plop&lt;/code&gt; or &lt;code&gt;npm plop&lt;/code&gt; to run &lt;code&gt;plop&lt;/code&gt;. You can name &lt;code&gt;&quot;plop&quot;&lt;/code&gt; whatever you want the command to be for example &lt;code&gt;&quot;generate&quot;: &quot;plop&quot;&lt;/code&gt; and it will behave the same way.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
 &quot;plop&quot;: &quot;plop&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unlike code snippets, plop doesn’t require additional setup to share between computers or across developers and lives in version control. Also, plop allows multiple files to be generated at once.&lt;/p&gt;
&lt;p&gt;Now we can create our first &lt;code&gt;plopfile.js&lt;/code&gt; in the root level of our directory which is where the plop magic will happen.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;plopfile.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = function (plop) {
  /* welcome messag that will display in CLI */
  plop.setWelcomeMessage(
    &quot;Welcome to plop! What type of file would you like to generate?&quot;,
  ),
    /* name and description of our template */
    plop.setGenerator(&quot;generate blog post ✏️&quot;, {
      description: &quot;Template for generating blog posts&quot;,

      prompts: [
        /* inquirer prompts */
        /* questions we want to ask in CLI and save questions for*/
      ],

      actions: [
        /* what should be generated based off of the above prompts */
      ],
    });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have a baseline &lt;code&gt;plopfile.js&lt;/code&gt; let&apos;s add some functionality. First we will add the ability to generate the frontmatter or metadata that needs to appear on every draft blog post in order for Gatsby to properly generate it.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sample frontmatter&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
title: Automating File Creation With JavaScript
pubDate: 2020-01-14T12:40:44.608Z
template: &quot;post&quot;
slug: 2020-01-14-automating-file-creation-with-javascript
category:
  - tutorial
description: This article walks through how to use plop a micro-generator to generate new text-based files.
---
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We should update the plop file to add built-in functionality to format today&apos;s date into an ISOString and use Inquirer (built into Plop) to create CLI prompts and collect input.
We will use &lt;code&gt;new Date(Date.now())&lt;/code&gt; to get the current date. We will format the date both as an &lt;code&gt;ISOStringDate&lt;/code&gt;: &lt;code&gt;2020-01-14T12:40:44.608Z&lt;/code&gt; and as a &lt;code&gt;shortDate&lt;/code&gt;: &lt;code&gt;2020-01-14&lt;/code&gt;. The &lt;code&gt;ISOStringDate&lt;/code&gt;
will be used in the frontmatter whereas the&lt;code&gt;shortDate&lt;/code&gt;will be used in the file path of the newly generated file. The date utils will be returned byset&lt;code&gt;plop.setHelper()&lt;/code&gt;in order to expose the values in our&lt;code&gt;.hbs&lt;/code&gt;templates by writing&lt;code&gt;{{ISOStringDate}}&lt;/code&gt;or&lt;code&gt;{{shortDate}}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In terms of collecting input in prompts the basic structure of a prompt is&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  // example inquirer types:
  // input, list, raw list, expandable list, checkbox, password, editor
  // learn more here: https://github.com/SBoudrias/Inquirer.js#prompt-types
  type: &quot;input&quot;,

  name: &quot;description&quot;,

  message: &quot;Description of post:&quot;,

  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The most complex prompt in this example is this list prompt which allows users to use arrow keys to select the category for their blog post and then we transform that value to a lowercase string. The &lt;code&gt;filter&lt;/code&gt; prompt can be used to convert a user-friendly value like &quot;yellow&quot; to being inserted in the template as &lt;code&gt;#ffff00&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; {
          type: &quot;list&quot;,
          name: &quot;category&quot;,
          message: &quot;Category:&quot;,
          choices: [&quot;Tutorial&quot;, &quot;Reflection&quot;],
          filter: function(val) {
            return val.toLowerCase()
          },
        },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once all of the prompts are squared away we need to do something with the input by adding an action:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
          type: &quot;add&quot;,
          path: `content/blog/${shortDate}-{{dashCase title}}.md`,
          templateFile: &quot;src/plop-templates/blog-post.hbs&quot;,
        },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A type of action &lt;code&gt;add&lt;/code&gt; creates a new file at the &lt;code&gt;path&lt;/code&gt; and interpolates the responses from the &lt;code&gt;prompts&lt;/code&gt; and values from plop helpers into the &lt;code&gt;templateFile&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The complete &lt;code&gt;plopfile.js&lt;/code&gt; at this point should look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = function (plop) {
  const today = new Date(Date.now());
  const shortDate = today.toISOString().split(&quot;T&quot;)[0];
  plop.setHelper(&quot;shortDate&quot;, () =&amp;gt; shortDate),
    plop.setHelper(&quot;ISOStringDate&quot;, () =&amp;gt; today.toISOString()),
    // optional welcome message

    plop.setWelcomeMessage(
      &quot;Welcome to plop! What type of file would you like to generate?&quot;,
    ),
    plop.setGenerator(&quot;blog post ✏️&quot;, {
      description: &quot;template for generating blog posts&quot;,
      prompts: [
        {
          type: &quot;input&quot;,
          name: &quot;title&quot;,
          message: &quot;Title of post:&quot;,
        },
        {
          type: &quot;input&quot;,
          name: &quot;description&quot;,
          message: &quot;Description of post:&quot;,
        },

        {
          type: &quot;list&quot;,
          name: &quot;category&quot;,
          message: &quot;Category:&quot;,
          choices: [&quot;Tutorial&quot;, &quot;Reflection&quot;],
          filter: function (val) {
            return val.toLowerCase();
          },
        },
      ],
      actions: [
        {
          type: &quot;add&quot;,
          path: `content/blog/${shortDate}-{{dashCase title}}.md`,
          templateFile: &quot;src/plop-templates/blog-post.hbs&quot;,
        },
      ],
    });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to actually use this we need to create the &lt;code&gt;blog-post.hbs&lt;/code&gt; template in our &lt;code&gt;src/plop-templates/&lt;/code&gt; directory. This &lt;code&gt;.hbs&lt;/code&gt; file is where we parametrize the code to only keep the bits that we need from file to file and to have placeholders for things that change based on the name or type of thing that is being generated. Plop has built-in case helpers like &lt;code&gt;titleCase&lt;/code&gt; or &lt;code&gt;dashCase&lt;/code&gt; to format input (view the built-in case modifiers at: https://plopjs.com/documentation/#case-modifiers)&lt;/p&gt;
&lt;p&gt;blog-post.hbs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--- title:
{{titleCase title}}
# from title prompt date:
{{ISOStringDate}}
# from plopHelper template: “post” draft: true slug:
{{shortDate}}-{{dashCase title}}
# from plop helper and title prompt category: -
{{category}}
# from category prompt description:
{{description}}
# from description prompt --- ## Intro
{{description}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;yarn plop&lt;/code&gt; now should walk you through the prompts we added and generate a new file based off of the responses to the prompts and the handlebars template. The file that was generated will
be at &lt;code&gt;content/blog/${shortDate}-{{dashCase title}}.md&lt;/code&gt; (or wherever you set the path in the &lt;code&gt;action&lt;/code&gt;).&lt;/p&gt;
&lt;h3&gt;Plop Example to Generate JSX Page&lt;/h3&gt;
&lt;p&gt;Below is an update plopfile and example handlebars template for generating a &lt;code&gt;Page.jsx&lt;/code&gt; and &lt;code&gt;Page.test.jsx&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Page.hbs&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot; // Components import { Helmet } from &quot;react-helmet&quot;
import { graphql } from &quot;gatsby&quot; import Layout from &quot;../components/page/layout&quot;
const
{{properCase pageName}}
= ({ data: { site: { siteMetadata: { title }, }, },}) =&amp;gt; (&amp;lt;Layout&amp;gt;

  &amp;lt;div&amp;gt;

    &amp;lt;Helmet title=&quot;{title}&quot; /&amp;gt;

  &amp;lt;/div&amp;gt;

&amp;lt;/Layout&amp;gt;) export default
{{properCase pageName}}

export const pageQuery = graphql` query { site { siteMetadata { title } } }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pageTest.hbs&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;

import { shallow } from &quot;enzyme&quot;

import Layout from &quot;../components/page/layout&quot;

import {{properCase pageName}} from &quot;./{{properCase pageName}}&quot;

import { Helmet } from &quot;react-helmet&quot;



const data = {

site: {

siteMetadata: {

title: “monica*dev”,

},

}

}



describe(“{{properCase pageName}}”, () =&amp;gt; {

const component = shallow(

&amp;lt;{{properCase pageName}} data={data} /&amp;gt;)



it(“renders page layout”, () =&amp;gt; {

expect(component.find(Layout)).toHaveLength(1)

})



it(“renders helmet with site title from site metadata”, () =&amp;gt; {

expect(component.find(Helmet).props().title).toBe(“monica*dev”)

})

})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;plopfile.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = function(plop) {

const today = new Date(Date.now())

const shortDate = today.toISOString().split(&quot;T&quot;)[0]

plop.setHelper(&quot;shortDate&quot;, () =&amp;gt; shortDate),

plop.setHelper(&quot;ISOStringDate&quot;, () =&amp;gt; today.toISOString()),

plop.setGenerator(&quot;generate blog post ✏️&quot;, {

 /*...*/

}),

plop.setGenerator(&quot;Create new page 📃&quot;, {

description: &quot;template for creating a new page&quot;,

prompts: [

{

type: &quot;input&quot;,

name: &quot;pageName&quot;,

message: &quot;Page name:&quot;,

},

],

actions: [

{

type: “add”,

path: “src/pages/{{properCase pageName}}.jsx”,

templateFile: “src/plop-templates/page.hbs”,

},

{

type: “add”,

path: “src/pages/{{camelCase pageName}}.test.jsx”,

templateFile: “src/plop-templates/pageTest.hbs”,

},

],

})

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Formatting Output&lt;/h2&gt;
&lt;p&gt;I ran into an issue where because the initial template files were &lt;code&gt;.hbs&lt;/code&gt; files the generated files weren&apos;t necessarily formatted like &lt;code&gt;.md&lt;/code&gt; or &lt;code&gt;.jsx&lt;/code&gt;. I already had prettier set up in my project so in order to solve the formatting issues I ended up updating my &lt;code&gt;plop&lt;/code&gt; script shorthand to do format all files after running plop. However, this should be refactored to only format the relevant, just generated files.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
  ...
  &quot;format&quot;: &quot;prettier —write \&quot;**/*.{js,jsx,json,md}\&quot;&quot;,
  &quot;plop&quot;: “plop &amp;amp;&amp;amp; yarn format”
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;As a recap, we used &lt;code&gt;plop&lt;/code&gt; to generate boilerplate code that is shared across certain types of files. Ideally implementing some type of automation for file creation should reduce time to create functional files, be less error-prone than copy + pasting + editing, encourages consistency and implementation of design patterns.&lt;/p&gt;
&lt;h3&gt;Create Your Own Template&lt;/h3&gt;
&lt;p&gt;Some ideas for things to incorporate in templates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create different templates based on the type of React component (examples in React-Boilerplate&apos;s generators)&lt;/li&gt;
&lt;li&gt;Generate comments or base-level documentation&lt;/li&gt;
&lt;li&gt;Generate self-contained directories or packages with, index file (and related tests), package.json and README.md&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Additional Resources&lt;/h3&gt;
&lt;p&gt;Last year, I had the opportunity to streamline the creation of new packages via CLI prompts (which inspired &lt;a href=&quot;https://noti.st/monica/AjCX04/automate-react-workflow&quot;&gt;my talk on generating React components&lt;/a&gt; at React Girls Conf in London) and led me to learn more about Plop. If you&apos;re interested in learning more about Plop in a React context or alternatives to Plop check out my previous talk.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/automate-react-workflow-sketchnotes.jpg#center&quot; alt=&quot;Sketchnotes from Monica&apos;s Automating React Workflow Talk at React Girls Conf in London&quot; /&gt;
&lt;em&gt;Sketchnotes by &lt;a href=&quot;https://twitter.com/malweene&quot;&gt;@malweene&lt;/a&gt; from my talk at React Girls Conf&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here are some additional resources that may be helpful as you are getting more familiar with generating files with Plop.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;https://github.com/plopjs/plop&lt;/li&gt;
&lt;li&gt;https://github.com/react-boilerplate/react-boilerplate/tree/master/internals/generators&lt;/li&gt;
&lt;li&gt;https://github.com/SBoudrias/Inquirer.js/&lt;/li&gt;
&lt;li&gt;https://handlebarsjs.com/&lt;/li&gt;
&lt;li&gt;https://prettier.io/&lt;/li&gt;
&lt;li&gt;https://github.com/M0nica/generate-kawaii-components&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Understanding Reduce in JavaScript</title><link>https://aboutmonica.com/blog/2020-03-29-understanding-reduce-in-javascript/</link><guid isPermaLink="true">https://aboutmonica.com/blog/2020-03-29-understanding-reduce-in-javascript/</guid><description>This article will walk through various ways to use ES6 reduce to map, filter and construct objects from arrays.</description><pubDate>Sun, 29 Mar 2020 22:21:35 GMT</pubDate><content:encoded>&lt;p&gt;Reducing an array is a helpful functional programming technique to use when you need to reduce multiple values into a single value. Although that &lt;em&gt;single&lt;/em&gt; value isn&apos;t limited to being an integer it can be an array, an object...etc. I&apos;ve found reduce to be a handy method to have at your disposal during technical interviews. This article will introduce ES6&apos;s &lt;code&gt;reduce()&lt;/code&gt; method and then walk through various ways to use reduce and discuss other built-in methods that abstract &lt;code&gt;reduce()&lt;/code&gt; like &lt;code&gt;map()&lt;/code&gt; and &lt;code&gt;filter()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The general structure when using &lt;code&gt;reduce()&lt;/code&gt; is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;input.reduce(callback, initialAccValue);
// Note: the `initialAccValue` is optional but effects how reduce() behaves
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When an &lt;code&gt;initialAccValue&lt;/code&gt; is supplied,the callback function is called on every item within the &lt;code&gt;input&lt;/code&gt; array. The callback function will then mutate the &lt;code&gt;accumulator&lt;/code&gt; until it has gone through every item and it reaches it final value. Thet final value of the &lt;code&gt;accumulator&lt;/code&gt; is ultimately the return value of &lt;code&gt;reduce()&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;input.reduce((accumulator, item) =&amp;gt; {
  // What operation(s) should occur for each item?
  // What will the accumulator value look like after this operation?
  // 🚨: the accumulator value needs to be explicitly returned.
}, initialValue);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reducer function can take up to four arguments and look like&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;arr.reduce(callback(accumulator, currentValue[, index[, array]] )[, initialValue])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/media/filter-fruit-example.png#center&quot; alt=&quot;illustrated example of using filter vs reduce to filter fruits out of a collection of food&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can read more about &lt;code&gt;reduce()&lt;/code&gt; and the &lt;code&gt;index&lt;/code&gt; and &lt;code&gt;array&lt;/code&gt; parameters at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce.&lt;/p&gt;
&lt;h2&gt;Reducing an array to a single number&lt;/h2&gt;
&lt;p&gt;A common way &lt;code&gt;reduce()&lt;/code&gt; can be used is to return the sum of all of the values in an array.&lt;/p&gt;
&lt;p&gt;In the below code each item in the &lt;code&gt;values&lt;/code&gt; array is added to the &lt;code&gt;accumulator&lt;/code&gt; in the callback function. The initial value of the accumulator is explicitly set to &lt;code&gt;0&lt;/code&gt;. During each iteration through the values in &lt;code&gt;input&lt;/code&gt; the accumulator grows larger since each time the callback function is called it is passed in the resulting &lt;code&gt;accumulator&lt;/code&gt; value from the previous iteration.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const input = [1, 100, 1000, 10000];
const sum = input.reduce((accumulator, item) =&amp;gt; {
  return accumulator + item;
}, 0); // 11101
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we walk through each iteration of the above &lt;code&gt;reduce&lt;/code&gt; method it will look something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;accumulator = 0, item = 1&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the returned &lt;code&gt;accumulator&lt;/code&gt; = 1 + 0 = 1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;accumulator = 1, item = 100&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the returned &lt;code&gt;accumulator&lt;/code&gt; = 1 + 100 = 101&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;accumulator = 101, item = 1000&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the returned &lt;code&gt;accumulator&lt;/code&gt; = 101 + 1000 = 1101&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;accumulator = 1101, item = 10000&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the final (and only number) returned from the reduce function is 11101. This number is reached because 1101 + 10000 = 11101 and 10000 is the last item in the &lt;code&gt;input&lt;/code&gt; array.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Mapping Items with &lt;code&gt;map()&lt;/code&gt; and &lt;code&gt;reduce()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;If you want to return a new array that is the result of performing an operation on each item in an array then you can use &lt;code&gt;map()&lt;/code&gt; or &lt;code&gt;reduce()&lt;/code&gt;. &lt;code&gt;map()&lt;/code&gt; allows you to more concisely accomplish this than &lt;code&gt;reduce()&lt;/code&gt; but either could be used. The example below shows how to return an array that contains squared versions of numbers in an original array using both &lt;code&gt;map()&lt;/code&gt; and &lt;code&gt;reduce()&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Using &lt;code&gt;map()&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const numbers = [1, 10, 100];
const squared = numbers.map((item) =&amp;gt; Math.pow(item, 2));
// [1, 100, 10000]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Using &lt;code&gt;reduce()&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const numbers = [1, 10, 100];
const squared = numbers.reduce((acc, number) =&amp;gt; {
  acc.push(Math.pow(number, 2));
  return acc;
}, []); // [1, 100, 10000]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Filtering Items with &lt;code&gt;filter()&lt;/code&gt; and &lt;code&gt;reduce()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;If you ever need to filter out values in an array to only retain values that meet certain criteria you can use either &lt;code&gt;filter()&lt;/code&gt; or construct a new array that only is compromised of items that pass the criteria. Below are examples of using &lt;code&gt;filter()&lt;/code&gt; and &lt;code&gt;reduce()&lt;/code&gt; to only return the even numbers from an array of digits.&lt;/p&gt;
&lt;h3&gt;Using &lt;code&gt;filter()&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evenNumbers = numbers.filter((number) =&amp;gt; number % 2 === 0);
// [2, 4, 6, 8, 10]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Using &lt;code&gt;reduce()&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evenNumbers = numbers.reduce((acc, number) =&amp;gt; {
  if (number % 2 == 0) {
    acc.push(number);
  }
  return acc;
}, []);
// [2, 4, 6, 8, 10]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Reducing an array to a single object&lt;/h2&gt;
&lt;p&gt;Although &lt;code&gt;reduce()&lt;/code&gt; has to return a single value but that single value doesn&apos;t have to be an integer or array it can also be an object. The following example will walk through how to reduce an array into a single object. This prompt was taken from &lt;a href=&quot;https://coursework.vschool.io/array-reduce-exercises/&quot;&gt;VSchool&apos;s reduce practice exercises&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The prompt was:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Given an array of potential voters, return an object representing the results of the vote. Include how many of the potential voters were in the ages 18-25, how many from 26-35, how many from 36-55, and how many of each of those age ranges actually voted. The resulting object containing this data should have 6 properties.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The input of voters looked like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const voters = [
  { name: &quot;Bob&quot;, age: 30, voted: true },
  { name: &quot;Jake&quot;, age: 32, voted: true },
  { name: &quot;Kate&quot;, age: 25, voted: false },
  { name: &quot;Sam&quot;, age: 20, voted: false },
  { name: &quot;Phil&quot;, age: 21, voted: true },
  { name: &quot;Ed&quot;, age: 55, voted: true },
  { name: &quot;Tami&quot;, age: 54, voted: true },
  { name: &quot;Mary&quot;, age: 31, voted: false },
  { name: &quot;Becky&quot;, age: 43, voted: false },
  { name: &quot;Joey&quot;, age: 41, voted: true },
  { name: &quot;Jeff&quot;, age: 30, voted: true },
  { name: &quot;Zack&quot;, age: 19, voted: false },
];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I ended up writing the &lt;code&gt;voterResults&lt;/code&gt; function below which takes in an array that is shaped like the &lt;code&gt;voters&lt;/code&gt; array above. I decided to set the initial accumulator value to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const initialVotes = {
  youngVotes: 0,
  youth: 0,
  midVotes: 0,
  mids: 0,
  oldVotes: 0,
  olds: 0,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;initialVotes&lt;/code&gt; object was the same shape as the final value I needed the &lt;code&gt;reduce()&lt;/code&gt; to return and encapsulated all of the potential keys that would be added and defaulted each to 0. This structure easily captures categories that ended up having a total of 0 at the end and also eliminated the need to have check that the current value of a key existed before incrementing it.&lt;/p&gt;
&lt;p&gt;Within the callback function logic needed to be implemented to determine which peer group someone was in based on conditional logic related to their age. That &lt;code&gt;peer&lt;/code&gt; data was also used to increment the voting data for their &lt;code&gt;peer&lt;/code&gt; group if they voted.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function voterResults(arr) {
  const initialVotes = {
    youngVotes: 0,
    youth: 0,
    midVotes: 0,
    mids: 0,
    oldVotes: 0,
    olds: 0,
  }

  const peersToVotePeers = {
    youth: &quot;youngVotes&quot;,
    mids: &quot;midVotes&quot;,
    olds: &quot;oldVotes&quot;,
  }

  return arr.reduce((acc, voter) =&amp;gt; {
    if(voter.age &amp;lt; 26)
      peers = &quot;youth&quot;
    } else if (voter.age &amp;lt; 36) {
      peers = &quot;mids&quot;
    } else {
      peers = &quot;olds&quot;
    }
    if (!voter.voted) {
      // if they didn&apos;t vote let&apos;s just increment their peers
      // for example for  { name: &quot;Zack&quot;, age: 19, voted: false }
      // The peer group would be &quot;youth&quot; and we&apos;d increment acc[&quot;youth&quot;] by one
      return { ...acc, [peers]: acc[peers] + 1 }
    } else {
      const votePeers = peersToVotePeers[peers];
      // for example for { name: &quot;Jeff&quot;, age: 30, voted: true }
      // The peer group would is &quot;mids&quot;
      // acc[&quot;mids&quot;] and acc[&quot;midVotes&quot;] are both incremented by one
      return {
        ...acc,
        [peers]: acc[peers] + 1,
        [votePeers]: acc[votePeers] + 1,
      }
    }
  }, initialVotes)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The object output from &lt;code&gt;voterResults(voters)&lt;/code&gt; is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{ youngVotes: 1,
  youth: 4,
  midVotes: 3,
  mids: 4,
  oldVotes: 3,
  olds: 4
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Often &lt;code&gt;accumulator&lt;/code&gt; is abbreviated as &lt;code&gt;acc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The callback function must explicitly return a value or else &lt;code&gt;reduce()&lt;/code&gt; will return &lt;code&gt;undefined&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Refactoring useState() To useReducer()</title><link>https://aboutmonica.com/blog/2020-04-04-refactoring-use-state-to-use-reducer/</link><guid isPermaLink="true">https://aboutmonica.com/blog/2020-04-04-refactoring-use-state-to-use-reducer/</guid><description>This article walks through how to combine multiple useState() React hooks logic into a single useReducer().</description><pubDate>Sat, 04 Apr 2020 17:06:08 GMT</pubDate><content:encoded>&lt;p&gt;I recently created a &lt;a href=&quot;https://stimulus-check-calculator.now.sh/&quot;&gt;Stimulus Check Calculator&lt;/a&gt; based on figures from the CARES Act and the Washington Post to help people estimate the amount of their stimulus check under the CARES Act.
This article will walk through how I refactored the calculator&apos;s state management by consolidating multiple &lt;code&gt;useState()&lt;/code&gt; React hooks into a single &lt;code&gt;useReducer()&lt;/code&gt;. &lt;code&gt;useReducer()&lt;/code&gt; is an alternative that can be considered when using &lt;code&gt;useState()&lt;/code&gt; to manage state in functional React components. This article assumes some familiarity with &lt;a href=&quot;https://reactjs.org/docs/state-and-lifecycle.html#adding-local-state-to-a-class&quot;&gt;state management in React&lt;/a&gt; and &lt;a href=&quot;https://reactjs.org/docs/hooks-reference.html&quot;&gt;React Hooks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/stimulus-check-calculator-screenshot.png#center&quot; alt=&quot;screenshot of the stimulus check calculator app&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Managing form state with useState();&lt;/h2&gt;
&lt;p&gt;In order to use the &lt;code&gt;useState()&lt;/code&gt; React hook for state management of the calculator I first needed to import &lt;code&gt;useState&lt;/code&gt; from React.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useState } from &quot;react&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Setting the initial state with useState()&lt;/h3&gt;
&lt;p&gt;Then within the function that is returning the &lt;code&gt;Form&lt;/code&gt; component I setup the &lt;code&gt;useState()&lt;/code&gt; hooks for &lt;code&gt;taxYear&lt;/code&gt;, &lt;code&gt;filingStatus&lt;/code&gt;,&lt;code&gt;income&lt;/code&gt;, &lt;code&gt;children&lt;/code&gt; and &lt;code&gt;stimulusAmount&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const { SINGLE, HEADOFHOUSE, MARRIED } = filingStatuses;
const [taxYear, setTaxYear] = useState(2019);
const [filingStatus, setFilingStatus] = useState(SINGLE);
const [income, setIncome] = useState(&quot;75000&quot;);
const [children, setChildren] = useState(0);
const [stimulusAmount, setStimulusAmount] = useState(-1);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The parameter passed into &lt;code&gt;useState()&lt;/code&gt; represents the default value for that particular state. Meaning that the below line is setting the default value of &lt;code&gt;taxYear&lt;/code&gt; in state to &lt;code&gt;2019&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const [taxYear, setTaxYear] = useState(2019);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Updating form state with useState()&lt;/h3&gt;
&lt;p&gt;Event handlers, such as &lt;code&gt;onChange&lt;/code&gt; or &lt;code&gt;onClick&lt;/code&gt; can be used to update the component&apos;s state when data in the form changes. Managing form state by updating the component&apos;s internal state is considered a &quot;&lt;a href=&quot;https://reactjs.org/docs/uncontrolled-components.html&quot;&gt;controlled component&lt;/a&gt;&quot; vs. having the DOM manage the state of the form.&lt;/p&gt;
&lt;p&gt;In order to update the &lt;code&gt;taxYear&lt;/code&gt;&apos;s value to the selected year, there&apos;s an &lt;code&gt;onClick&lt;/code&gt; event handler that calls &lt;code&gt;setTaxYear(year)&lt;/code&gt; with the &lt;code&gt;year&lt;/code&gt; parameter being the current &lt;code&gt;year&lt;/code&gt; that is selected.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  [2019, 2018].map((year) =&amp;gt; (
    &amp;lt;button
      onClick={() =&amp;gt; setTaxYear(year)}
      className={year == taxYear ? &quot;selectedButton&quot; : &quot;&quot;}
      key={year}
      name=&quot;tax-year&quot;
    &amp;gt;
      {year == 2019 ? &quot;Yes&quot; : &quot;No&quot;}
    &amp;lt;/button&amp;gt;
  ));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similar logic is used to update &lt;code&gt;filingStatus&lt;/code&gt; &lt;code&gt;income&lt;/code&gt; and &lt;code&gt;children&lt;/code&gt;, &lt;code&gt;stimulusAmount&lt;/code&gt; and &lt;code&gt;handleSubmit&lt;/code&gt; when form data is updated or submitted.&lt;/p&gt;
&lt;h2&gt;Managing form state with useReducer();&lt;/h2&gt;
&lt;p&gt;In order to use the &lt;code&gt;useReducer()&lt;/code&gt; React hook for state management of the calculator I first needed to import &lt;code&gt;useReducer&lt;/code&gt; from React. If you are not familiar with &lt;code&gt;reducers&lt;/code&gt; in JavaScript check out my article on &lt;a href=&quot;/blog/2020-03-29-understanding-reduce-in-javascript&quot;&gt;Understanding Reduce in Javascript&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useReducer } from &quot;react&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Setting the initial state with useReducer()&lt;/h3&gt;
&lt;p&gt;Then I set the initial state for the component like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const initialState = {
  taxYear: 2019,
  filingStatus: SINGLE,
  income: &quot;75000&quot;,
  children: 0,
  stimulusAmount: -1,
};

const [state, dispatch] = useReducer(reducer, initialState);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similar to &lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;useReducer&lt;/code&gt; returns the related state along with a method to update the state. With &lt;code&gt;useReducer&lt;/code&gt; instead of updating the state by passing a value to &lt;code&gt;setState()&lt;/code&gt; an action should be dispatched which will call the &lt;code&gt;reducer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In my case the &lt;code&gt;reducer&lt;/code&gt; function looked like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function reducer(state, action) {
  const { type, payload } = action;
  return { ...state, [type]: payload };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Updating form state with useReducer()&lt;/h3&gt;
&lt;p&gt;Each time &lt;code&gt;dispatch&lt;/code&gt; is called it should be called with an &lt;code&gt;action&lt;/code&gt; item that contains a &lt;code&gt;type&lt;/code&gt; and in this particular case a &lt;code&gt;payload&lt;/code&gt; as well. The state of tax year can be updated &lt;code&gt;onClick&lt;/code&gt; by firing&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; onClick={() =&amp;gt; dispatch({ type: &quot;taxYear&quot;, payload: year })}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;instead of&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  onClick={() =&amp;gt; setTaxYear(year)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;reducer(state, action)&lt;/code&gt; is expecting to receive an &lt;code&gt;action&lt;/code&gt; that is an object with &lt;code&gt;type&lt;/code&gt; and &lt;code&gt;payload&lt;/code&gt;. Within the reducer function the action&apos;s &lt;code&gt;type&lt;/code&gt; and &lt;code&gt;payload&lt;/code&gt; are used to return the current &lt;code&gt;state&lt;/code&gt; with the &lt;code&gt;[type]: payload&lt;/code&gt; overwritten.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const { type, payload } = action;
return { ...state, [type]: payload };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the case of updating the state from 2017 if the current state was:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const initialState = {
  taxYear: 2019,
  filingStatus: SINGLE,
  income: &quot;75000&quot;,
  children: 0,
  stimulusAmount: -1,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then firing &lt;code&gt;onClick={() =&amp;gt; dispatch({ type: &quot;taxYear&quot;, payload: 2018 })}&lt;/code&gt; would result in the reducer returning the current state but with only the value of &lt;code&gt;taxYear&lt;/code&gt; overwritten and set to 2018. Note: this works as written because for each action in this example the &lt;code&gt;type&lt;/code&gt; of action is the same as its corresponding key value in &lt;code&gt;state&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Full Source Code of Examples&lt;/h2&gt;
&lt;p&gt;The full source code below compares the full implementations of the state management methods above. As was illustrated above, &lt;code&gt;useReducer()&lt;/code&gt; is another React hook that can be used for state management and can be implemented in a way that allows logic from &lt;code&gt;useState()&lt;/code&gt; hooks to be consolidated. The related source code for the current version of the calculator is available on &lt;a href=&quot;https://github.com/M0nica/stimulus-check-calculator&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;source code using useState():&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import { filingStatuses } from &quot;../utils/constants&quot;;
import { getStimulusAmount } from &quot;../utils/calculateStimulus&quot;;
import { useState } from &quot;react&quot;;

function Form() {
  const { SINGLE, HEADOFHOUSE, MARRIED } = filingStatuses;
  const [taxYear, setTaxYear] = useState(2019);
  const [filingStatus, setFilingStatus] = useState(SINGLE);
  const [income, setIncome] = useState(&quot;75000&quot;);
  const [children, setChildren] = useState(0);
  const [stimulusAmount, setStimulusAmount] = useState(-1);

  function handleSubmit(e) {
    e.preventDefault();
    setStimulusAmount(calculateStimulus(income, filingStatus, children));
  }

  return (
    &amp;lt;form onSubmit={handleSubmit}&amp;gt;
      &amp;lt;label htmlFor=&quot;tax-year&quot;&amp;gt;Have you filed your 2019 taxes yet?&amp;lt;/label&amp;gt;
      {[2019, 2018].map((year) =&amp;gt; (
        &amp;lt;button
          onClick={() =&amp;gt; setTaxYear(year)}
          className={year == taxYear ? &quot;selectedButton&quot; : &quot;&quot;}
          key={year}
          name=&quot;tax-year&quot;
        &amp;gt;
          {year == 2019 ? &quot;Yes&quot; : &quot;No&quot;}
        &amp;lt;/button&amp;gt;
      ))}
      &amp;lt;label htmlFor=&quot;filing-status&quot;&amp;gt;
        What was your filing status in your {taxYear} taxes?{&quot; &quot;}
      &amp;lt;/label&amp;gt;
      {[SINGLE, MARRIED, HEADOFHOUSE].map((status) =&amp;gt; (
        &amp;lt;button
          onClick={() =&amp;gt; setFilingStatus(status)}
          className={status == filingStatus ? &quot;selectedButton&quot; : &quot;&quot;}
          name=&quot;filing-status&quot;
          key={status}
        &amp;gt;
          {&quot; &quot;}
          {status}
        &amp;lt;/button&amp;gt;
      ))}
      &amp;lt;br /&amp;gt;
      &amp;lt;label htmlFor=&quot;adjusted-income&quot;&amp;gt;
        What was your adjusted gross income in {taxYear}?
      &amp;lt;/label&amp;gt;
      ${&quot; &quot;}
      &amp;lt;input
        type=&quot;number&quot;
        inputMode=&quot;numeric&quot;
        pattern=&quot;[0-9]*&quot;
        value={income}
        onChange={(e) =&amp;gt; setIncome(e.target.value)}
        min={0}
        name=&quot;adjusted-income&quot;
      /&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;label htmlFor=&quot;children&quot;&amp;gt;
        How many children under age 17 did you claim as dependents in {taxYear}?
      &amp;lt;/label&amp;gt;
      &amp;lt;input
        type=&quot;number&quot;
        inputMode=&quot;numeric&quot;
        pattern=&quot;[0-9]*&quot;
        value={children}
        onChange={(e) =&amp;gt; setChildren(e.target.value)}
        min={0}
        name=&quot;label&quot;
      /&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;button type=&quot;submit&quot; className=&quot;calculateButton&quot;&amp;gt;
        Calculate
      &amp;lt;/button&amp;gt;
      &amp;lt;p&amp;gt;
        {&quot; &quot;}
        {stimulusAmount &amp;gt;= 0 &amp;amp;&amp;amp;
          (stimulusAmount &amp;gt; 0
            ? `Your stimulus amount is expected to be $${stimulusAmount}.`
            : `You are not expected to receive a stimulus.`)}
      &amp;lt;/p&amp;gt;
      &amp;lt;br /&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}

export default Form;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;source code using useReducer():&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import { useReducer } from &quot;react&quot;;
import { filingStatuses } from &quot;../utils/constants&quot;;
import { getStimulusAmount } from &quot;../utils/calculateStimulus&quot;;

function reducer(state, action) {
  const { type, payload } = action;
  return { ...state, [type]: payload };
}

function Form() {
  const { SINGLE, HEADOFHOUSE, MARRIED } = filingStatuses;

  const initialState = {
    taxYear: 2019,
    filingStatus: SINGLE,
    income: &quot;75000&quot;,
    children: 0,
    stimulusAmount: -1,
  };

  const [state, dispatch] = useReducer(reducer, initialState);

  function handleSubmit(e) {
    e.preventDefault();
    dispatch({
      type: &quot;stimulusAmount&quot;,
      payload: getStimulusAmount(income, filingStatus, children),
    });
  }

  const { taxYear, filingStatus, income, children, stimulusAmount } = state;

  return (
    &amp;lt;form onSubmit={handleSubmit}&amp;gt;
      &amp;lt;label htmlFor=&quot;tax-year&quot;&amp;gt;Have you filed your 2019 taxes yet?&amp;lt;/label&amp;gt;
      {[2019, 2018].map((year) =&amp;gt; (
        &amp;lt;button
          onClick={() =&amp;gt; dispatch({ type: &quot;taxYear&quot;, payload: year })}
          className={year == taxYear ? &quot;selectedButton&quot; : &quot;&quot;}
          key={year}
          name=&quot;tax-year&quot;
        &amp;gt;
          {year == 2019 ? &quot;Yes&quot; : &quot;No&quot;}
        &amp;lt;/button&amp;gt;
      ))}
      &amp;lt;label htmlFor=&quot;filing-status&quot;&amp;gt;
        What was your filing status in your {taxYear} taxes?{&quot; &quot;}
      &amp;lt;/label&amp;gt;
      {[SINGLE, MARRIED, HEADOFHOUSE].map((status) =&amp;gt; (
        &amp;lt;button
          onClick={() =&amp;gt; dispatch({ type: &quot;filingStatus&quot;, payload: status })}
          className={status == filingStatus ? &quot;selectedButton&quot; : &quot;&quot;}
          name=&quot;filing-status&quot;
          key={status}
        &amp;gt;
          {&quot; &quot;}
          {status}
        &amp;lt;/button&amp;gt;
      ))}
      &amp;lt;br /&amp;gt;
      &amp;lt;label htmlFor=&quot;adjusted-income&quot;&amp;gt;
        What was your adjusted gross income in {taxYear}?
      &amp;lt;/label&amp;gt;
      ${&quot; &quot;}
      &amp;lt;input
        type=&quot;string&quot;
        inputMode=&quot;numeric&quot;
        pattern=&quot;[0-9]*&quot;
        value={income}
        onChange={(e) =&amp;gt; dispatch({ type: &quot;income&quot;, payload: e.target.value })}
        min={0}
      /&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;label htmlFor=&quot;children&quot;&amp;gt;
        How many children under age 17 did you claim as dependents in {taxYear}?
      &amp;lt;/label&amp;gt;
      &amp;lt;input
        type=&quot;number&quot;
        inputMode=&quot;numeric&quot;
        pattern=&quot;[0-9]*&quot;
        value={children}
        onChange={(e) =&amp;gt;
          dispatch({ type: &quot;children&quot;, payload: e.target.value })
        }
        min={0}
        name=&quot;label&quot;
      /&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;button type=&quot;submit&quot; className=&quot;calculateButton&quot;&amp;gt;
        Calculate
      &amp;lt;/button&amp;gt;
      &amp;lt;p&amp;gt;
        {&quot; &quot;}
        {stimulusAmount &amp;gt;= 0 &amp;amp;&amp;amp;
          (stimulusAmount &amp;gt; 0
            ? `Your stimulus amount is likely to be ${new Intl.NumberFormat(
                &quot;en-US&quot;,
                { style: &quot;currency&quot;, currency: &quot;USD&quot; },
              ).format(stimulusAmount)}.`
            : `You are not expected to receive a stimulus.`)}
      &amp;lt;/p&amp;gt;
      &amp;lt;br /&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}

export default Form;
&lt;/code&gt;&lt;/pre&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Top Gatsby Plugins For Developer Blogs</title><link>https://aboutmonica.com/blog/2020-04-19-essential-gatsby-plugins-for-blogs/</link><guid isPermaLink="true">https://aboutmonica.com/blog/2020-04-19-essential-gatsby-plugins-for-blogs/</guid><description>I&apos;ve published dozens of articles on my Gatsby site and these are the top plugins I recommend to enhance your blog&apos;s SEO and user experience.</description><pubDate>Sun, 19 Apr 2020 17:44:36 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve published dozens of articles on this site, which was built using &lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;Gatsby&lt;/a&gt;. In the past year I decided to invest more time into maintaining my blog and want to share the top plugins I recommend to enhance your Gatsby blog. Gatsby has a &lt;a href=&quot;https://www.gatsbyjs.org/plugins/&quot;&gt;vast library of plugins&lt;/a&gt; which are Node packages that are designed to work with Gatsby APIs to extend the functionality of Gatsby sites.&lt;/p&gt;
&lt;p&gt;I was motivated to invest more in maintaining my site because as a creator on the web it&apos;s important to have a place that you have full control over that houses all of your content. More and more developers are migrating back to self-hosting their blogs and thus retaining greater autonomy and ownership over their content.&lt;/p&gt;
&lt;p&gt;I encourage all developers who choose to blog and have the time/resources to not only maintain their blog but to consider cross-posting to sites like &lt;a href=&quot;https://dev.to&quot;&gt;dev.to&lt;/a&gt;. If you&apos;re just getting started with syndicating content check out this piece by Stephanie Morillo on &lt;a href=&quot;https://www.stephaniemorillo.co/post/how-blog-post-syndication-works&quot;&gt;How Blog Post Syndication Works&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;Tweet tweetLink=&quot;paulg/status/1251537525145042953?s&quot;/&amp;gt;&lt;/p&gt;
&lt;h2&gt;Recommended Gatsby Plugins&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-google-analytics/&quot;&gt;gatsby-plugin-google-analytics&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One of the features I appreciate from &lt;a href=&quot;https://medium.com/&quot;&gt;Medium&lt;/a&gt; is that they provide content authors with access to analytics for content that they post. This information can be helpful when tracking which content resonates more with your audience and get a better gauge on how people are finding your site. If you would like access to metrics like page views, average session duration, etc. then you need to pro-actively install analytics as data usually cannot be collected retroactively. If you are interested in using Google Analytics in particular then you should check out &lt;code&gt;gatsby-plugin-google-analytics&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Alternative: &lt;a href=&quot;Tweet%20tweetLink=%22JoshWComeau/status/1248266542745354242?s%22/&quot;&gt;Adding a hit counter to your site&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-react-helmet/&quot;&gt;gatsby-plugin-react-helmet&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Speaking of using &lt;code&gt;gatsby-plugin-google-analytics&lt;/code&gt; or a hit counter to learn more about your site&apos;s traffic, an essential part of creating discoverable web content is optimizing for SEO. The &lt;code&gt;gatsby-plugin-react-helmet&lt;/code&gt; is built on-top of &lt;a href=&quot;https://github.com/nfl/react-helmet&quot;&gt;react-helmet&lt;/a&gt; to support server-side rendered SEO. It allows you to configure a site title, description, and other metadata. Updating metadata is essential to providing visually striking previews of content when content is shared on Twitter, Facebook, Slack, text messages and more!&lt;/p&gt;
&lt;p&gt;Also, if your experience is anything like mine then debugging metadata images is sometimes an adventure and you may need to use tools like the &lt;a href=&quot;https://cards-dev.twitter.com/validator&quot;&gt;Twitter card validator&lt;/a&gt; and the &lt;a href=&quot;https://developers.facebook.com/tools/debug/&quot;&gt;Facebook sharing debugger&lt;/a&gt; to debug image previews.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-social-cards/&quot;&gt;gatsby-plugin-my-social-cards&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One way to make your blog stand out more is to have custom images for each blog post vs. a generic-site wide image. Manually creating new images for each blog post can be time-consuming and depending on your strategy is not necessary. I ended up installing &lt;code&gt;gatsby-plugin-my-social-cards&lt;/code&gt; to generate custom images for each blog post.&lt;/p&gt;
&lt;p&gt;In my case I have a base image that looks like:
&lt;img src=&quot;/media/base.jpg#center&quot; alt=&quot;base image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;and then for each article that I post I use &lt;code&gt;gatsby-plugin-my-social-cards&lt;/code&gt; to generate an image that combines my base image with the title of the article resulting in something that looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/twitter-preview-image-example.jpeg#center&quot; alt=&quot;example image preview that would appear on twitter&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gatsbyjs.org/packages/@weknow/gatsby-remark-twitter/&quot;&gt;gatsby-remark-twitter&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you want to embed relevant tweets like the ones that appear throughout this article into markdown content on your site then check out the &lt;code&gt;gatsby-remark-twitter&lt;/code&gt; plugin.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-remark-prismjs/&quot;&gt;gatsby-remark-prismjs&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the audience for your blog is developers then there&apos;s a good chance that some of your articles will feature code snippets. If so, &lt;code&gt;gatsby-remark-prismsjs&lt;/code&gt; as an extension of &lt;a href=&quot;https://prismjs.com/&quot;&gt;PrismJS&lt;/a&gt; allows you to set up and style language-specific code syntax highlighting.&lt;/p&gt;
&lt;p&gt;Example snippet with PrismJS syntax highlighting:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function formatReadingTime(minutes) {
  let buckets = Math.round(minutes / 5);

  return `${new Array(buckets || 1).fill(&quot;🍿&quot;).join(&quot;&quot;)} ${minutes} min read`;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-remark-autolink-headers/&quot;&gt;gatsby-remark-autolink-headers&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;gatsby-remark-autolink-headers&lt;/code&gt; allows you to automatically add anchor tags to all article headers. This allows these headers to be linkable similar to how Markdown files render on Github.com by default. This autolink functionality is helpful for allowing people to share links to particular sections of your article and helps make the process of creating table of contents more efficient.&lt;/p&gt;
&lt;p&gt;These are 6 of the Gatsby plugins I&apos;ve found essential to enhancing my Gatsby blog and providing it with better SEO and a richer experience for my audience. The next time I set up a blog using Gatsby I&apos;d consider installing all of these plugins depending on the particular needs of the site.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Exploring Art Direction With Gatsby-Image</title><link>https://aboutmonica.com/blog/2020-06-24-exploring-art-direction-in-gatsby/</link><guid isPermaLink="true">https://aboutmonica.com/blog/2020-06-24-exploring-art-direction-in-gatsby/</guid><description>I recently used Gatsby&amp;#x27;s Image&amp;#x27;s Art direction to update my header to display different images based on viewport size.</description><pubDate>Wed, 24 Jun 2020 00:51:34 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;While updating my homepage to include my newly-commission illustration from &lt;a href=&quot;https://toondoon1010.carbonmade.com/&quot;&gt;Aishwarya Tandon&lt;/a&gt;, I wanted to load different versions of the image based on screen-size in a performant way. For my header image, I use &lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-image&quot;&gt;gatsby-image&lt;/a&gt;, an official GatsbyJS plugin that optimizes images that makes it straightforward to control how images are progressively enhanced on page load. One of my favorite built-in Gatsby image effects is &amp;lt;a href=&quot;https://using-gatsby-image.gatsbyjs.org/traced-svg/&quot;&amp;gt;traced SVGs&amp;lt;/a&amp;gt;, which renders an SVG tracing of an image, in a pre-determined color before the image fully loads. You can explore all of gatsby-image&apos;s built-in loading effects on the &lt;a href=&quot;https://using-gatsby-image.gatsbyjs.org/&quot;&gt;using-gatsby-image&lt;/a&gt; site.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/header-traced-svg-example.gif&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;The above GIF illustrates how gatsby-image transitions from traced SVG to the loaded image&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While looking into how to load different images at different breakpoints without unnecessarily downloading files I discovered gatsby-image&apos;s built-in support for this feature which is known as &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images#Art_direction&quot;&gt;art direction&lt;/a&gt;. Modern HTML can be used to dynamically load of images based on screen size using &lt;code&gt;srcset&lt;/code&gt; attributes with &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; instead of JavaScript.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout&amp;gt;
Swapping images at different screen sizes can be done with JavaScript or CSS
instead of native HTML attributes however using the HTML attributes can
improve page loading performance as it prevents unnecessarily preloading two
images when only one image from the &lt;code&gt;srcset&lt;/code&gt; needs to be visible within the
viewport on load.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/header.gif&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;The above GIF shows the transition between different images loaded with art direction within gatsby-image&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;All images that use the gatsby-image plugin need to be constructed using a GraphQL query. Below is a slightly modified version of the &lt;code&gt;useStaticQuery()&lt;/code&gt; I used to load image files into gatsby-image&apos;s &lt;code&gt;Img&lt;/code&gt;. Within &lt;code&gt;useStaticQuery()&lt;/code&gt; I wrote two named queries &lt;code&gt;mobileImage&lt;/code&gt; and &lt;code&gt;desktopImage&lt;/code&gt; to load both versions of the image I wanted on mobile as well as the desktop.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import { useStaticQuery, graphql } from &quot;gatsby&quot;;
import Img from &quot;gatsby-image&quot;;

const Avatar = () =&amp;gt; {
  const data = useStaticQuery(graphql`
    query {
      mobileImage: file(
        relativePath: { eq: &quot;animonica-headshot-cropped.png&quot; }
      ) {
        childImageSharp {
          fluid(maxWidth: 300, quality: 100) {
            ...GatsbyImageSharpFluid_tracedSVG
          }
        }
      }
      desktopImage: file(relativePath: { eq: &quot;animonica-full.png&quot; }) {
        childImageSharp {
          fluid(maxWidth: 300, quality: 100) {
            ...GatsbyImageSharpFluid_tracedSVG
          }
        }
      }
    }
  `);

  const sources = [
    data.mobileImage.childImageSharp.fluid,
    {
      ...data.desktopImage.childImageSharp.fluid,
      media: `(min-width: 625px)`,
    },
  ];

  return &amp;lt;Img fluid={sources} alt=&quot;Illustrated Monica&quot; /&amp;gt;;
};

export default Avatar;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you inspect my site&apos;s header using Dev Tools you&apos;ll notice there&apos;s two &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; sections, one that includes the tracedSVGs that are generated by &lt;code&gt;GatsbyImageSharpFluid_tracedSVG&lt;/code&gt; in the image query and another with the &lt;code&gt;.png&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;The HTML that ultimately renders to load the .png files from the earlier use of gatsby-image resembles the below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;picture&amp;gt;
  &amp;lt;source media=&quot;(min-width: 625px)&quot; srcset=&quot;animonica-full.png&quot; /&amp;gt;
  &amp;lt;source srcset=&quot;animonica-headshot-cropped.png&quot; /&amp;gt;
  &amp;lt;img src=&quot;animonica-full.png&quot; alt=&quot;Illustrated Monica&quot; /&amp;gt;
&amp;lt;/picture&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The media attribute in the &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements, within &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;, determines which image &lt;code&gt;srcset&lt;/code&gt; is displayed. The first condition that is met will be displayed, in the case where neither media condition is true &lt;em&gt;or&lt;/em&gt; the browser doesn&apos;t support the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element it will return the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element from within the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;. You can view current browser compatibility for &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; at &lt;a href=&quot;https://caniuse.com/#feat=picture&quot;&gt;Can I use this?&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As a recap, within &lt;code&gt;gatsby-image&lt;/code&gt; we can pass an array of images to generate HTML&apos;s art direction related attributes. Using this approach instead of CSS or JavaScript reduces the number of assets that need to be preloaded in the browser. My current Gatsby site, displays a complete HTML page from the server to the client on the initial load without requiring JavaScript therefore I&apos;ve found it helpful to avoid delegating certain tasks to JavaScript that HTML/CSS can handle in a more performant way. I&apos;ve written more about this topic in my article &lt;a href=&quot;/blog/less-javascript-is-more&quot;&gt;Less JavaScript Makes Font Awesome More Awesome&lt;/a&gt;.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>4 Meetups That Helped Me Grow as A Woman of Color in Tech</title><link>https://aboutmonica.com/blog/4-meetups-that-helped-me-grow/</link><guid isPermaLink="true">https://aboutmonica.com/blog/4-meetups-that-helped-me-grow/</guid><description>Attending various tech Meetups over the years has been pivotal to my growth as a technologist</description><pubDate>Thu, 26 Jul 2018 17:11:11 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/media/monicaillustration.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Attending various tech Meetups over the years has been pivotal to my growth as a technologist&lt;/h2&gt;
&lt;p&gt;I took a leap and left my full-time job as an email marketer, to work to pursue
software engineering! This summer I kicked off my engineering career as a Full
Stack Engineering Intern at &lt;a href=&quot;https://medium.com/@meetup&quot;&gt;Meetup&lt;/a&gt; with the
Meetups at &lt;a href=&quot;https://medium.com/@WeWork&quot;&gt;WeWork&lt;/a&gt; team; we are responsible for
allowing organizers to seamlessly book space to host their Meetup events. I
strongly encourage event organizers to check it out and learn more
&lt;a href=&quot;https://help.meetup.com/hc/en-us/articles/360004656412-What-is-Meetups-WeWork-&quot;&gt;here&lt;/a&gt;
as this service is currently free.&lt;/p&gt;
&lt;p&gt;&amp;lt;Tweet tweetLink=&quot;heif/status/1016336592141733890&quot;/&amp;gt;&lt;/p&gt;
&lt;p&gt;Attending various Meetups in New York City over the years has been pivotal to my
growth as a technologist and has led me to go to technical talks and conferences
that covered topics beyond my comfort zone, pushed me to contribute my writing
to multiple Medium publications, helped me to go from student to teaching
assistant to teacher and even served as a valuable resource during the technical
interview prep process.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/monica-teaching.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;span class=&quot;figcaption_hack&quot;&amp;gt;A photo of me teaching a workshop on publishing a blog with Jekyll at
&lt;a href=&quot;https://medium.com/@CodeNewbies&quot;&gt;CodeNewbie&lt;/a&gt;’s Codeland 2018 conference.&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I’d like to share some of the tech-related Meetups that I’ve benefitted the most
from in recent years that have helped me transition from marketing into
engineering.&lt;/p&gt;
&lt;h4&gt;1. Women Who Code NYC&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/@WomenWhoCode&quot;&gt;Women Who Code&lt;/a&gt; (WWC) is “an international
non-profit organization that provides services for women pursuing technology
careers.”&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What I love about WWC? I really enjoy one of their signature Meetups which
revolves around solving whiteboarding problems. Whiteboarding algorithms
commonly comes up in technical interviews, and practicing with other people in a
supportive environment helps make them a little less intimidating and allows you
to exercise the skills that are needed to do well when solving coding challenges
during an interview.&lt;/li&gt;
&lt;li&gt;Algorithms! Meet monthly and go through various problems on a whiteboard in a
small group working in the same language.&lt;/li&gt;
&lt;li&gt;Pro-Tip — Go to their next Algorithms Meetup even if you don’t think you’re
“ready” yet! You can check out &lt;a href=&quot;https://medium.com/@WomenWhoCodeNYC&quot;&gt;Women Who Code
NYC&lt;/a&gt;’s GitHub
(&lt;a href=&quot;https://github.com/WomenWhoCodeNYC/Algorithms&quot;&gt;https://github.com/WomenWhoCodeNYC/Algorithms&lt;/a&gt;)
for a sneak peak of the type of problems they generally ask and sneak in some
practice.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;2&lt;/strong&gt;. Girl Develop It NYC&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/@girldevelopit&quot;&gt;Girl Develop It&lt;/a&gt; (GDI) is hands-down one of
my favorite tech non-profits (right up there with Black Girls Code). They
recently celebrated having over 100,000 members across the country.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GDI hosts workshops for adults learning how to code with their &lt;a href=&quot;https://www.girldevelopit.com/materials&quot;&gt;open-sourced
curriculum&lt;/a&gt; that covers everything from
general web concepts to React, Pandas, Python, HTML/CSS and more. Their classes
are generally more affordable than similar offerings at for-profit entities.&lt;/li&gt;
&lt;li&gt;Pro-tip: Don’t let the cost deter you, as they offer scholarships for &lt;em&gt;every&lt;/em&gt;
class. Every event they host with a cost associated has a link to apply for the
scholarship; individuals are eligible to be awarded up to 4 Girl Develop It NYC
scholarships in a year.&lt;/li&gt;
&lt;li&gt;What I love about GDI? They provide a supportive, judgement-free place for
individuals new to coding and those looking to teach what they know to others.
My local GDI’s leadership encouraged me to get out of my “comfort zone” and
serve as a teaching assistant which was a very rewarding experience and helped
me become more confident in my technical skills. Additionally, I’m a fan of Girl
Develop It NYC’s weekly newsletter (exclusive to their Meetup members) which
features interesting coding libraries and relevant opportunities.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3. Write/Speak/Code NYC&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/@writespeakcode&quot;&gt;Write/Speak/Code&lt;/a&gt; (W/S/C) works “to
&lt;strong&gt;increase the visibility and leadership of women and non-binary coders&lt;/strong&gt;
through thought leadership, conference speaking, open source contributions,
career development, personal growth and self-care.”&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;W/S/C primarily hosts career development events that revolve around writing,
public speaking, open source contributions and other professional development
topics.&lt;/li&gt;
&lt;li&gt;Pro-tip: I was going to say grab a ticket to their upcoming 4 day conference but
it already sold out ;) So my tip would be to plan ahead and consider attending
their next conference!&lt;/li&gt;
&lt;li&gt;What I love about W/S/C? I strongly encourage anyone with even a hint of
imposter syndrome to look into W/S/C. I went to a one day event they hosted in
NYC last Fall, &lt;a href=&quot;https://github.com/WriteSpeakCode/wsc-resources/tree/master/own-your-expertise&quot;&gt;Own Your
Expertise&lt;/a&gt;,
and was really challenged to examine how I view leaders in the workplace. I left
that event feeling empowered by knowing that with my current skillset and
knowledge I already have valuable expertise to share.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;4. Black Software Engineers NYC&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;This group is run by We Build Black, which is a non-profit organization that
focuses on creating safe spaces for Black technologists and creating educational
programs.&lt;/li&gt;
&lt;li&gt;Pro-tip: They frequently host free workshops geared at those learning a new
programming language or technology (React Lifecycle, Python Programming, Intro
to JavaScript, etc.). You can sign up for an upcoming class at
&lt;a href=&quot;https://www.meetup.com/Black-Software-Engineers-of-NYC/&quot;&gt;https://www.meetup.com/Black-Software-Engineers-of-NYC/&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;What I love about Black Software Engineers NYC: They have created a supportive
environment for individuals from various backgrounds and if you are just getting
started with programming you will be in great company! Of all of the Meetups
I’ve attended over the years I strongly believe this one is one of the most
community-oriented ones.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;Not in New York?&lt;/h3&gt;
&lt;p&gt;These organizations have a presence in multiple cities and may have an active
chapter near you!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.meetup.com/pro/writespeakcode&quot;&gt;Write/Speak/Code&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://www.webuildblack.com/&quot;&gt;We Build Black&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.meetup.com/pro/girldevelopit&quot;&gt;Girl Develop It&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.womenwhocode.com/&quot;&gt;Women Who Code&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Adding Environment Variables To GitHub Actions</title><link>https://aboutmonica.com/blog/adding-environment-variables-to-github-actions/</link><guid isPermaLink="true">https://aboutmonica.com/blog/adding-environment-variables-to-github-actions/</guid><description>This article  walks through how to pass environment variables to GitHub Action Workflows</description><pubDate>Sun, 23 Aug 2020 22:58:47 GMT</pubDate><content:encoded>&lt;p&gt;This article walks through how to pass environment variables to &lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub Action&lt;/a&gt; Workflows. I recently set up GitHub actions on this site to automatically run unit tests whenever I open a new Pull Request to ensure that changes to the site were not introduction breaking changes to unit tests under the radar.&lt;/p&gt;
&lt;p&gt;I ran into some hiccups when setting it up so that the workflow could install the Font Awesome dependency which requires an NPM token. I went through a few of my GitHub Actions build minutes while testing how to properly pass environment variables to my &lt;code&gt;.github/workflow/test.yml&lt;/code&gt; file. Hopefully this article saves you some time and build minutes the next time you set up environment variables for GitHub Actions.&lt;/p&gt;
&lt;p&gt;In order to setup my GitHub Action, I modeled my new GitHub Action workflow after similar functional workflows and expected everything to run smoothly. However, I merged the pull request with my changes and saw on the Pull Request that the step of the GitHub Action workflow that installed dependencies failed with the following error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;error An unexpected error occurred: &quot;Failed to replace env in config: $
{FONTAWESOME_NPM_AUTH_TOKEN}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then I remembered 💡 my site requires an environment variable to build and deploy. Locally I have &lt;a href=&quot;https://docs.npmjs.com/configuring-npm/npmrc.html&quot;&gt;&lt;code&gt;.npmrc&lt;/code&gt;&lt;/a&gt; file that sets up the the npm configuration to pass in the appropriate environment variable for my site and looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@fortawesome:registry=https://npm.fontawesome.com/
//npm.fontawesome.com/:_authToken=${FONTAWESOME_NPM_AUTH_TOKEN}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In particular the GitHub Action workflow did not have access to the &lt;code&gt;FONTAWESOME_NPM_AUTH_TOKEN&lt;/code&gt; which I have set in my &lt;em&gt;local&lt;/em&gt; bash profile and passed into the &lt;code&gt;.npmrc&lt;/code&gt; file. So I needed to give the GitHub repository that is running this actions access to the environment variable by going to its settings page. You can access settings by clicking &quot;Settings&quot; in the repository&apos;s navigation. The url to update secrets for a repository (at the time of this writing) looks like: https://github.com/username/repo/settings/secrets where username and repo should be replaced with the username and repo of the desired repository and clicking &quot;new secret&quot;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/github-env-setup.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Above is a screenshot of what the secrets page under my repository settings looked like once I added the &lt;code&gt;FONTAWESOME_NPM_AUTH_TOKEN&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, that my repository had access to the environment variable I needed to update each job within my action to also have access to the variables. Before adding the environment variable to my repository my GitHub Action the job that installed dependencies looked like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; - name: Install Dependencies
      run: npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;after adding the environment variable I updated the dependency installation job to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Install Dependencies
      run: |
        npm config set //npm.fontawesome.com/:_authToken=${FONTAWESOME_NPM_AUTH_TOKEN}
        npm ci
      env:
        FONTAWESOME_NPM_AUTH_TOKEN: ${{ secrets.FONTAWESOME_NPM_AUTH_TOKEN }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;npm config set&lt;/code&gt; allowed me to manually set the npm configuration outside of the &lt;code&gt;.npmrc&lt;/code&gt; file and pass in the appropriate environment variable. I also updated &lt;code&gt;npm install&lt;/code&gt; to &lt;code&gt;npm ci&lt;/code&gt; which installs a project with a clean slate and is better suited for a CI environment. Finally after the &lt;code&gt;run&lt;/code&gt; values I passed in a &lt;code&gt;env&lt;/code&gt; key that contained &lt;code&gt;FONTAWESOME_NPM_AUTH_TOKEN&lt;/code&gt; which referenced &lt;code&gt;${{ secrets.FONTAWESOME_NPM_AUTH_TOKEN }}&lt;/code&gt; a.k.a the &lt;code&gt;secrets&lt;/code&gt; from &lt;em&gt;this&lt;/em&gt; GitHub repository.&lt;/p&gt;
&lt;p&gt;The full GitHub action workflow I ended up using to install all dependencies (including Font Awesome which required a token ) and running unit tests looked like the below. Note: the test command also needed access to the same environment variable and needed it to be set separately in order for the GitHub Action to successfully run without running into the error we saw earlier with retrieving the environment variable.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: CI

on:
# will run on all PRs that are opened or updated (synchronized)
pull_request:
  types: [opened, synchronize, reopened]

jobs:
test:
  name: Test
  runs-on: ubuntu-latest
  env:
    CI: true
  steps:
    - uses: actions/checkout@v2
    - name: Use Node.js 13.x
      uses: actions/setup-node@v1
      with:
        node-version: 13.x
    - name: Install Dependencies
      run: | # run multiple commands
        npm config set //npm.fontawesome.com/:_authToken=${FONTAWESOME_NPM_AUTH_TOKEN}
        npm ci
      env:
        FONTAWESOME_NPM_AUTH_TOKEN: ${{ secrets.FONTAWESOME_NPM_AUTH_TOKEN }}
    - name: Test
      run: | # run multiple commands
        npm config set //npm.fontawesome.com/:_authToken=${FONTAWESOME_NPM_AUTH_TOKEN}
        npm run test
      env:
        FONTAWESOME_NPM_AUTH_TOKEN: ${{ secrets.FONTAWESOME_NPM_AUTH_TOKEN }}
```
&lt;/code&gt;&lt;/pre&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>How to Add Instagram Timeline To a NextJS Site</title><link>https://aboutmonica.com/blog/adding-instagram-timeline-to-next-js-site/</link><guid isPermaLink="true">https://aboutmonica.com/blog/adding-instagram-timeline-to-next-js-site/</guid><description>This article walks through how to integrate Instagram on a NextJS site.</description><pubDate>Mon, 21 Dec 2020 22:10:20 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;danger&quot;&amp;gt;
Due to changes in the Instagram API the method for displaying Instagram photos
in this article currently &amp;lt;b&amp;gt;does not work&amp;lt;/b&amp;gt; due to CORS issues. I recommend{&quot; &quot;}
&amp;lt;a href=&quot;https://developers.facebook.com/docs/development/&quot;&amp;gt;
applying for access to Facebook/Instagram APIs
&amp;lt;/a&amp;gt;{&quot; &quot;}
for stable access to content hosted on Instagram.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;I recently added an Instagram timeline to my site &lt;a href=&quot;https://www.indigitalcolor.com/&quot;&gt;In Digital Color&lt;/a&gt; and wanted to share how I added the Instagram integration to my NextJS site and document some of the hiccups I encountered along the way with authentication.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/in-digital-color-ig-integration-screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot of Instagram Timeline integration on indigitalcolor.com&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Install and import &lt;code&gt;instagram-web-api&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;We&apos;ll use the &lt;a href=&quot;https://github.com/jlobos/instagram-web-api&quot;&gt;&lt;code&gt;instagram-web-api&lt;/code&gt;&lt;/a&gt; npm package to do the heavy lifting for the integration. which should be installed within a NextJS site with &lt;code&gt;npm install instagram-web-api&lt;/code&gt; or &lt;code&gt;yarn add instagram-web-api&lt;/code&gt;. Once the package is successfully installed it should be imported into &lt;code&gt;index.js&lt;/code&gt; file (or whichever page you would like the Instagram feed to appear on) with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Instagram from &quot;instagram-web-api&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point the &lt;code&gt;index.js&lt;/code&gt; file where I imported &lt;code&gt;instagram-web-api&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Instagram from &quot;instagram-web-api&quot;;
import Layout from &quot;../components/layout&quot;;

export default function Index() {
  return &amp;lt;Layout&amp;gt;{/*Other Page Content*/}&amp;lt;/Layout&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Setup getStaticProps to Fetch Data at Build Time&lt;/h2&gt;
&lt;p&gt;Next, we need to set up a way to get fetched data into our component. In this case, I chose to use the async &lt;a href=&quot;https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation&quot;&gt;&lt;code&gt;getStaticProps&lt;/code&gt;&lt;/a&gt; function to pass data into our component.&lt;code&gt;getStaticProps&lt;/code&gt; is a function available to pages within NextJS that allows data to be fetched at build time and passed into page-level components.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Layout from &quot;../components/layout&quot;;
import Instagram from &quot;instagram-web-api&quot;;

// empty array of instagram posts is being
// passed as a prop into the Index component
export default function Index({ instagramPosts }) {
  return &amp;lt;Layout&amp;gt;{/*Other Page Content*/}&amp;lt;/Layout&amp;gt;;
}

export async function getStaticProps(context) {
  // set posts to an empty array as a placeholder
  let posts = [];
  return {
    props: {
      // return the posts as the prop instagramPosts
      // for the Index function to use
      instagramPosts: posts,
    },
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Setup Instagram Client Authentication&lt;/h2&gt;
&lt;p&gt;Before we can retrieve data from Instagram we should set up authentication &lt;code&gt;instagram-web-api&lt;/code&gt; in &lt;code&gt;getStaticProps()&lt;/code&gt; to be able to fetch data from Instagram. We can accomplish this by setting up a new Instagram client with &lt;code&gt;new Instagram({username: IG_USERNAME, password: IG_PASSWORD})&lt;/code&gt; and attempting to log in with &lt;code&gt;await client.login()&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export async function getStaticProps(context) {
  // create a new client to communicate with  Instagram
  // this service requires authentication
  //with username and password parameters
  const client = new Instagram({
    username: process.env.IG_USERNAME,
    password: process.env.IG_PASSWORD,
  });

  let images = [];
  try {
    // attempt to log in to Instagram
    await client.login();
  } catch (err) {
    // throw an error if login to Instagram fails
    console.log(&quot;Something went wrong while logging into Instagram&quot;, err);
  }

  return {
    props: {
      instagramPosts: images,
    },
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Credentials such as the password should be accessed via environment variables that are only available locally and at build time to avoid exposing this information. In NextJS, local environment variables can be accessed by creating a file entitled &lt;code&gt;.env.development.local&lt;/code&gt;. Although the username is not a secret you can add it to the environment file if you&apos;d like. &lt;code&gt;env.development.local&lt;/code&gt; should be added to your &lt;code&gt;.gitignore&lt;/code&gt; file to avoid committing it to version control. Separately, I set up my hosting to have access to the appropriate secrets at build time which supersedes the values in the &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;My &lt;code&gt;env.development.local&lt;/code&gt; file looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IG_USERNAME=&quot;super_cool_username&quot;
IG_PASSWORD=&quot;super_secret_secure_password&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In addition to the &lt;code&gt;env.development.local&lt;/code&gt; at a minimum you should have a &lt;code&gt;.env&lt;/code&gt; file that sets the defaults for env variables. My &lt;code&gt;.env&lt;/code&gt; file looks like and is checked into version control:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IG_USERNAME=&quot;&quot;
IG_PASSWORD=&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Confirm Instagram Client is Functional&lt;/h2&gt;
&lt;p&gt;If you&apos;re not already you should run the development server of your NextJS site with &lt;code&gt;npm run next&lt;/code&gt; and check in the terminal that is running the server to see if there are any console errors. You might encounter the error log we added of &quot;Something went wrong while logging into Instagram&quot; during this stage of the process.&lt;/p&gt;
&lt;h2&gt;Resolving 4xx Authentication Errors&lt;/h2&gt;
&lt;p&gt;While I was setting up the Instagram client with the correct credentials I encountered various 4xx authentication relates errors (if you&apos;re not familiar with HTTP status codes check out my site https://www.httriri.com/) being returned to Instagram saying &quot;Please wait a few minutes before trying again&quot;. I was able to temporarily circumnavigate the 4xx errors and &quot;Please wait a few minutes before trying again&quot; message by switching my IP address with a VPN (Virtual private network) and attempting to authenticate again. A VPN allows you to access the internet with a remote VPN server&apos;s IP address as opposed to your local IP address. &lt;a href=&quot;https://www.comparitech.com/blog/vpn-privacy/change-ip-address/&quot;&gt;This article&lt;/a&gt; shares other ways you can change your IP address. I already had a VPN configured on my computer so I found that to be the most straightforward solution in my case. Using a VPN to access the internet resolved the issues I was encountering as they were IP address level issues. You can check if your issues are IP level by trying to log into Instagram manually on the same IP address and on another device that is connected to a separate cellular network (and has its own IP). I am wondering if there&apos;s a more official way to get Instagram to recognize API usage as not being unusual activity. If you are running into a 4xx error for a different reason this is a good time to manually confirm that the credentials you are providing are correct.&lt;/p&gt;
&lt;h2&gt;Request Timeline Data from Instagram&lt;/h2&gt;
&lt;p&gt;Once authentication is successful we can request specific data from Instagram calling &lt;code&gt;client.getPhotosByUsername({username: process.env.IG_USERNAME})&lt;/code&gt; to fetch timeline data for the username that is passed in.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export async function getStaticProps(context) {
  const client = new Instagram({
    username: process.env.IG_USERNAME,
    password: process.env.IG_PASSWORD,
  });

  let posts = [];
  try {
    await client.login();
    // request photos for a specific instagram user
    const instagram = await client.getPhotosByUsername({
      username: process.env.IG_USERNAME,
    });

    if (instagram[&quot;user&quot;][&quot;edge_owner_to_timeline_media&quot;][&quot;count&quot;] &amp;gt; 0) {
      // if we receive timeline data back
      //  update the posts to be equal
      // to the edges that were returned from the instagram API response
      posts = instagram[&quot;user&quot;][&quot;edge_owner_to_timeline_media&quot;][&quot;edges&quot;];
    }
  } catch (err) {
    console.log(
      &quot;Something went wrong while fetching content from Instagram&quot;,
      err,
    );
  }

  return {
    props: {
      instagramPosts: posts, // returns either [] or the edges returned from the Instagram API based on the response from the `getPhotosByUsername` API call
    },
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Render Instagram Data with InstagramFeed component&lt;/h2&gt;
&lt;p&gt;Once we&apos;ve successfully received timeline data back from the &lt;code&gt;getPhotosByUsername()&lt;/code&gt; call we can render this data. Below is the skeleton I used to transform the data returned from the Instagram API into a digestible and visually engaging component that displays recent photos and links to them on Instagram so that visitors can click through to the actual posts.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Link from &quot;next/link&quot;;

export default function InstagramFeed({ instagramPosts }) {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h2&amp;gt;
        &amp;lt;a href=&quot;https://www.instagram.com/yourinstagramhandle/&quot;&amp;gt;
          Follow Us on Instagram
        &amp;lt;/a&amp;gt;
        .
      &amp;lt;/h2&amp;gt;

      &amp;lt;ul&amp;gt;
        {/* let&apos;s iterate through each of the
         instagram posts that were returned
         from the Instagram API*/}
        {instagramPosts.map(({ node }, i) =&amp;gt; {
          return (
            // let&apos;s wrap each post in an anchor tag
            // and construct the url for the post using
            // the shortcode that was returned from the API
            &amp;lt;li&amp;gt;
              &amp;lt;a
                href={`https://www.instagram.com/p/${node.shortcode}`}
                key={i}
                aria-label=&quot;view image on Instagram&quot;
              &amp;gt;
                {/* set the image src equal to the image
                url from the Instagram API*/}
                &amp;lt;img
                  src={node.thumbnail_src}
                  alt={
                    // the caption with hashtags removed
                    node.edge_media_to_caption.edges[0].node.text
                      .replace(/(#\w+)+/g, &quot;&quot;)
                      .trim()
                  }
                /&amp;gt;
              &amp;lt;/a&amp;gt;
            &amp;lt;/li&amp;gt;
          );
        })}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to actually render this component and the associated post data from Instagram we need to import &lt;code&gt;InstagramFeed&lt;/code&gt; into &lt;code&gt;index.js&lt;/code&gt; and render it within the &lt;code&gt;Index&lt;/code&gt; component with the &lt;code&gt;instagramPosts&lt;/code&gt; prop we received from &lt;code&gt;getStaticProps&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Layout from &quot;../components/layout&quot;;
import Instagram from &quot;instagram-web-api&quot;;
import InstagramFeed from &quot;../components/instagramFeed&quot;;
// empty array of instagram posts is being
// passed as a prop into the Index component
export default function Index({ instagramPosts }) {
  return (
    &amp;lt;Layout&amp;gt;
      {/*Other Page Content*/}
      &amp;lt;InstagramFeed instagramPosts={instagramPosts} /&amp;gt;
    &amp;lt;/Layout&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If everything worked properly you should now see all of your latest Instagram posts rendered on the page and be able to click any given posts to be taken to it on Instagram. If you are not seeing any posts rendering there may have been an issue with authentication (invalid credentials or rate limited by Instagram). If so, I recommend following my above troubleshooting steps of manually verifying credentials (in case of invalid credentials) or exploring using a VPN (if your IP address has been rate limited).&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Advent of Code - Day 01 2022</title><link>https://aboutmonica.com/blog/advent-of-code-12-01-2022/</link><guid isPermaLink="true">https://aboutmonica.com/blog/advent-of-code-12-01-2022/</guid><description>This article walks through how I solved Day 1 of Advent of Code 2022</description><pubDate>Sun, 04 Dec 2022 11:25:50 GMT</pubDate><content:encoded>&lt;p&gt;&apos;Tis the season of &lt;a href=&quot;https://adventofcode.com/&quot;&gt;Advent of Code&lt;/a&gt; which is an annual challenge of holiday-themed puzzles to solve with code. I usually attempt a few of the problems each year; the puzzles tend to get progressively more challenging each day and sometimes build on top of solutions from puzzles on earlier days. Each day you can earn up to ⭐️⭐️ (two stars) by solving both Part 1 and Part 2 (which adds a twist to part 1).&lt;/p&gt;
&lt;p&gt;To keep things interesting, Advent of Code gives different users different values in their input files to test their solution. Once you feel that you&apos;ve solved a problem you can run the function against the input file that AoC provided and then see if the output matches what AoC expected. They&apos;ll generally tell you if your answer is too high/too low to help nudge you in the right direction.&lt;/p&gt;
&lt;h2&gt;Helper Functions&lt;/h2&gt;
&lt;p&gt;I find that because Advent of Code gives large input files it&apos;s helpful to have helper functions to parse the input into your solution. Three of the helper functions I&apos;ve used in previous years are&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Splits the input into an array of strings
export const strInput = (data) =&amp;gt; data.split(&quot;\n&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This converts a file of&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
2
3
4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;into an array of strings like &lt;code&gt;[&apos;1&apos;,2&apos;,3&apos;,4&apos;]&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Splits the input into an array of numbers
export const numInput = (data) =&amp;gt; data.split(&quot;\n&quot;).map(Number);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This converts a file of&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
2
3
4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;into an array of numbers like &lt;code&gt;[1,2,3,4]&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Day One: Part One&lt;/h2&gt;
&lt;p&gt;For Day One: Part One of Advent of Code we are tasked with determining which elf is carrying the highest number of calories (check out the &lt;a href=&quot;https://adventofcode.com/2022/day/1&quot;&gt;official AoC prompt&lt;/a&gt; for a more fun description of the problem) and the calories are provided as a list of integers in a text file and each group (a.k.a set of numbers with a blank space) belongs to a different elf.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/*example input*/
1000
2000
3000

4000

5000
6000

7000
8000
9000

10000
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;This list represents the Calories of the food carried by five Elves: The first Elf is carrying food with 1000, 2000, and 3000 Calories, a total of 6000 Calories. The second Elf is carrying one food item with 4000 Calories. The third Elf is carrying food with 5000 and 6000 Calories, a total of 11000 Calories. The fourth Elf is carrying food with 7000, 8000, and 9000 Calories, a total of 24000 Calories. The fifth Elf is carrying one food item with 10000 Calories. (Source: https://adventofcode.com/2022/day/1 )&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Reading the data&lt;/h3&gt;
&lt;p&gt;Once we&apos;re ready to solve the problem we need access to our input data in a format that is easy to manipulate with JavaScript. We&apos;ll be using Node&apos;s File System API to read our locally downloaded input text file and will convert the file from a list of numbers to an an array of numbers with the help of the &lt;code&gt;numInput()&lt;/code&gt; helper function.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* node boilerplate to access file system*/
import { createRequire } from &quot;module&quot;;
const require = createRequire(import.meta.url);
const fs = require(&quot;fs&quot;);

/* import helper functions for parsing our text file into an array in order to use JavaScript array methods*/
import * as h from &quot;../helpers.js&quot;;

function getHighestCalorieCount() {
  /* use Node File System API to store output of local txt file in a string variable  */
  const data = fs.readFileSync(&quot;../2022/inputs/day_1.txt&quot;).toString();

  /* convert our string input into an array with numerical data*/
  const caloriesForElves = h.numInput(data);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Finding Highest Number of Calories Carried by an Elf&lt;/h3&gt;
&lt;p&gt;We can find the Elf with the highest calories by iterating through the list of calories and keeping track of the current total for each elf. We&apos;ll know that we&apos;ve finished counting the calories for an individual elf when the current calorie value is 0, as that is what the value the helper function converts the blank spaces for grouping in the original file to. If the current elf&apos;s total is higher than the highest total that we&apos;ve seen then we should store that value in &lt;code&gt;highestTotal&lt;/code&gt; where it will persist until we encounter a higher total. If the current elf&apos;s total is lower than the highest total when we&apos;re ready to move onto the next elf then we&apos;ll no longer track that total as we know that isn&apos;t the highest total number of calories.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// part 1
function getHighestCalorieCount() {
  const data = fs.readFileSync(&quot;../2022/inputs/day_1.txt&quot;).toString();

  const caloriesForElves = h.numInput(data);

  let currentTotal = 0; // tracks total for current elf
  let highestTotal = 0; // tracks highest total seen so far

  for (let entry of caloriesForElves) {
    /* when we reach a new elf&apos;s list of calories the value is == 0 and we should update the highestTotal seen so far if the current elf&apos;s total is higher than the highest total we&apos;ve seen so far and then always reset the currentTotal to 0 before moving onto the next elf.*/
    if (entry == 0) {
      if (currentTotal &amp;gt; highestTotal) {
        highestTotal = currentTotal;
      }
      currentTotal = 0;
    }
    /* increase currentTotal for the current elf */
    currentTotal += entry;
  }

  /* return the highest value between the currentTotal and highestTotal. This is necessary in case the currentTotal in the last iteration is the highest, because the line to update the highestTotal is called before the next group that is iterated through and is last called before the second to last group. */
  return Math.max(currentTotal, highestTotal);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Day One: Part Two&lt;/h2&gt;
&lt;h3&gt;Finding The Three Highest Number of Calories Carried by the Elves&lt;/h3&gt;
&lt;p&gt;Part two is a variation of Part One, and in my case required a different approach to storing the highest 3 totals vs. the approach I took for finding the 1 highest total. With this approach I decided to store the total for each elf in an array and then after iterating through all of the calorie values, sort the array from highest to lowest value and then return the sum of the 3 highest values. In the first solution, I opted to avoid an unnecessary sort but though sorting made the second problem easier to approach. However, I did carry over the logic of how the currentTotal is reset when encountering a new Elf (i.e., &lt;code&gt;entry == 0&lt;/code&gt;)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// part 2
function getTopThreeHighestCalorieCounts() {
  const data = fs.readFileSync(&quot;../2022/inputs/day_1.txt&quot;).toString();

  const caloriesForElves = h.numInput(data);

  let currentTotal = 0;
  let totalsForEachElf = []; /* stores sum of calories for each elf */

  for (let entry of caloriesForElves) {
    if (entry == 0) {
      totalsForEachElf.push(currentTotal);
      currentTotal = 0;
    }
    currentTotal += entry;
  }

  // grab the first second and third value from the computed totals
  const [first, second, third] = totalsForEachElf.sort((a, b) =&amp;gt; b - a);

  // reduce the array of first,second and third values to sum up their values; equivalent to first + second + third
  return [first, second, third].reduce((acc, count) =&amp;gt; {
    return acc + count;
  }, 0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Full Source Code in my AoC GitHub Repository: https://github.com/M0nica/advent-of-code/blob/main/2022/day_1_calorie_counting.js&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Fetch Webmentions Automatically with GitHub Actions</title><link>https://aboutmonica.com/blog/automate-webmentions-with-github-actions/</link><guid isPermaLink="true">https://aboutmonica.com/blog/automate-webmentions-with-github-actions/</guid><description>An overview of how in addition to using Brid.gy and Webmentions.io I&apos;m adding GitHub Actions and soon Webmentions.app (or its closely related CLI tool) to automate updating my website with the latest Webmentions and sending outgoing Webmentions to other websites.</description><pubDate>Fri, 14 Mar 2025 18:12:21 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;As I&apos;m re-writing this site in &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;, I&apos;m re-implementing features such as  Webmentions [^1]. Webmentions are a web standard that enables cross-site interactions including likes, replies, bookmarks, and reposts. This article walks through setting up an automated workflow that retrieves new Webmentions on a schedule, stores them as JSON files, and opens pull requests for review. This automation is largerly powered by a Node.js script and GitHub Actions. The goal of this workflow is to keep the Webmention data that is displayed relatively up-to-date while also having moderation in place before any any unreviewed content can become visible.&lt;/p&gt;
&lt;h2&gt;Displaying Webmentions&lt;/h2&gt;
&lt;p&gt;Webmentions on this site appear near the bottom of each article as avatars for likes, reposts, and bookmarks, and as comments for replies and mentions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/webmention-likes-and-shares.png&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;Avatars for likes and shares — each generated from the Webmention author&apos;s profile&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;&quot;wm-property&quot;&lt;/code&gt; field in each Webmention&apos;s metadata determines how it will be displayed. For &lt;code&gt;&quot;like-of&quot;&lt;/code&gt;, &lt;code&gt;&quot;repost-of&quot;&lt;/code&gt;, and &lt;code&gt;&quot;bookmark-of&quot;&lt;/code&gt;, only the author avatar is shown. Whereas, when the type of &lt;code&gt;&quot;wm-property&lt;/code&gt; is &lt;code&gt;&quot;in-reply-to&quot;&lt;/code&gt; or &lt;code&gt;&quot;mention-of&quot;&lt;/code&gt;, then response includes a &lt;code&gt;&quot;content&quot;&lt;/code&gt; field with the actual text, so both the avatar and the text are rendered.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/webmention-discussion.png&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;Threaded comments from replies and mentions&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Example webmention.io data for a &quot;Like&quot;&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;type&quot;: &quot;entry&quot;,
  &quot;author&quot;: {
    &quot;type&quot;: &quot;card&quot;,
    &quot;name&quot;: &quot;Katherine Johnson&quot;,
    &quot;url&quot;: &quot;https://example.com&quot;,
    &quot;photo&quot;: &quot;https://example.com/avatar.jpg&quot;
  },
  &quot;url&quot;: &quot;https://example.com/posts/liked-post&quot;,
  &quot;published&quot;: &quot;2025-04-24T12:00:00-04:00&quot;,
  &quot;wm-received&quot;: &quot;2025-04-24T12:01:00-04:00&quot;,
  &quot;wm-id&quot;: 123456,
  &quot;wm-source&quot;: &quot;https://example.com/posts/liked-post&quot;,
  &quot;wm-target&quot;: &quot;https://targetsite.com/original-post&quot;,
  &quot;wm-property&quot;: &quot;like-of&quot;,
  &quot;wm-private&quot;: false,
  &quot;like-of&quot;: &quot;https://targetsite.com/original-post&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Example webmention.io data for a &quot;Reply&quot;&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;type&quot;: &quot;entry&quot;,
  &quot;author&quot;: {
    &quot;type&quot;: &quot;card&quot;,
    &quot;name&quot;: &quot;Katherine Johnson&quot;,
    &quot;url&quot;: &quot;https://example.com&quot;,
    &quot;photo&quot;: &quot;https://example.com/avatar.jpg&quot;
  },
  &quot;url&quot;: &quot;https://example.com/posts/reply-to-post&quot;,
  &quot;published&quot;: &quot;2025-04-24T12:05:00-04:00&quot;,
  &quot;content&quot;: {
    &quot;text&quot;: &quot;Really enjoyed your post—this part about webmentions was especially helpful!&quot;
  },
  &quot;in-reply-to&quot;: &quot;https://targetsite.com/original-post&quot;,
  &quot;wm-received&quot;: &quot;2025-04-24T12:06:00-04:00&quot;,
  &quot;wm-id&quot;: 123457,
  &quot;wm-source&quot;: &quot;https://example.com/posts/reply-to-post&quot;,
  &quot;wm-target&quot;: &quot;https://targetsite.com/original-post&quot;,
  &quot;wm-property&quot;: &quot;in-reply-to&quot;,
  &quot;wm-private&quot;: false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;h2&gt;Setting Up the Webmention Pipeline&lt;/h2&gt;
&lt;p&gt;A few years ago, I was pair programming with Jason Lengstorf on Twitch to &lt;a href=&quot;../getting-started-with-webmention-next-js/&quot;&gt;add Webmentions to a Next.js site&lt;/a&gt;. During the stream we used two Webmention related services:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://webmention.io/&quot;&gt;Webmention.io&lt;/a&gt; to receive and store incoming Webmentions.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://brid.gy/&quot;&gt;Brid.gy&lt;/a&gt; to retrieve Webmentions from &lt;s&gt;Twitter&lt;/s&gt; BlueSky.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I still recommend both of these services, especially if you&apos;re interested in being able to capture Webmention data from social media sites. Instead of maintaining a custom Webmention receiver endpoint, these third-party services handle the infrastructure. For those interested in a self-hosted alternative, a couple of developers have written about their approaches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dwayne.xyz/post/webmentions-from-scratch&quot;&gt;Adding Webmention Support from Scratch&lt;/a&gt; by Dwayne Harris&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jamesg.blog/2021/09/22/webmention-receiver&quot;&gt;Building my own webmention receiver&lt;/a&gt; by James&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Getting started with webmention.io just requires adding two lines to the site&apos;s &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; to advertise the endpoint:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; &amp;lt;link rel=&quot;webmention&quot; href=&quot;https://webmention.io/username/webmention&quot; /&amp;gt;
 &amp;lt;link rel=&quot;pingback&quot; href=&quot;https://webmention.io/username/xmlrpc&quot; /&amp;gt;
 ```

## Writing the Fetch Script

Brid.gy and Webmention.io are used to remotely store and retrieve the Webmention while [GitHub Actions](https://github.com/features/actions) handles the automation layer of exporting Webmention data and  creating a persistent backup to enable displaying the Webmention data on the site without any client-side API calls.

Since this is a static site, the goal is to fetch Webmentions only at build time with no client-side data fetching. The solution is based on [Peter Goes&apos; approach](https://www.petergoes.nl/blog/review-webmentions-before-publishing-with-github-actions/), which opens a PR for each batch of new mentions. This process makes review easy before anything goes live. Webmention.io also supports deleting mentions and blocking specific domains for additional moderation needs. The script and config below are derivatives of Peter Goes&apos; work, with dependency updates and a few workflow tweaks.

The script lives at `automations/store-webmentions.js` and can run locally or via GitHub Actions to retrieve recent Webmentions from Webmention.io.

```js

const apiEndpoint = &quot;https://webmention.io/api/mentions.jf2&quot;;
const apiOptions = [
`token=${process.env.WEBMENTION_API_KEY}`,
&quot;per-page=10000&amp;amp;&quot;,
`since=${since.toISOString()}`,
];

fetch(`${apiEndpoint}?${apiOptions.join(&quot;&amp;amp;&quot;)}`)
.then(convertResponseToJson)
.then(checkDataValidity)
.then(get(&quot;children&quot;))
.then(filter(targetIsNotHomepage))
.then(forEach(writeMentionToFile));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each mention is then saved to &lt;code&gt;mentions/&lt;/code&gt; at the project root, in a subfolder matching the article&apos;s URL path, named by its &lt;code&gt;&quot;wm-id&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const targetFolder = path.join(process.cwd(), &quot;mentions&quot;);
function writeMentionToFile(mention) {
  
  const outputFolder = path.join(targetFolder, target);

  mkdirp.sync(outputFolder);

  fs.writeFileSync(
    path.join(outputFolder, `${id}.json`),
    JSON.stringify(mention, null, 2),
    { encoding: &quot;utf-8&quot; },
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;View full script to fetch Webmentions from Webmention.io&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import fs from &quot;fs&quot;;
import path from &quot;path&quot;;
import fetch from &quot;node-fetch&quot;;
import filter from &quot;lodash/fp/filter.js&quot;;
import get from &quot;lodash/fp/get.js&quot;;
import forEach from &quot;lodash/fp/forEach.js&quot;;
import { mkdirp } from &quot;mkdirp&quot;;
import dotenv from &quot;dotenv-save&quot;;

dotenv.config();

/* fetch webmentions from the past week 
   in case there was a delay in webmention.io 
   receiving webmentions or a disruption 
   in running this script */
const since = new Date();
since.setDate(since.getDate() - 7);

const domain = YOUR_DOMAIN_NAME;
const targetFolder = path.join(process.cwd(), &quot;mentions&quot;);
const apiEndpoint = &quot;https://webmention.io/api/mentions.jf2&quot;;
const apiOptions = [
  `token=${process.env.WEBMENTION_API_KEY}`,
  &quot;per-page=10000&amp;amp;&quot;,
  `since=${since.toISOString()}`,
];

fetch(`${apiEndpoint}?${apiOptions.join(&quot;&amp;amp;&quot;)}`)
  .then(convertResponseToJson)
  .then(checkDataValidity)
  .then(get(&quot;children&quot;))
  .then(filter(targetIsNotHomepage))
  .then(forEach(writeMentionToFile));

function convertResponseToJson(response) {
  return response.json();
}

function checkDataValidity(data) {
  if (&quot;children&quot; in data) return data;

  throw new Error(&quot;Invalid webmention.io response.&quot;);
}

function targetIsNotHomepage(mention) {
  const targetUri = mention[&quot;wm-target&quot;].replace(domain, &quot;&quot;);
  return targetUri !== &quot;/&quot; &amp;amp;&amp;amp; targetUri !== &quot;&quot;;
}

function writeMentionToFile(mention) {
  const id = mention[&quot;wm-id&quot;];
  const target = mention[&quot;wm-target&quot;]
    .replace(domain, &quot;&quot;)
    .replace(&quot;https://www.&quot;, &quot;&quot;)
    .replace(&quot;https:/&quot;, &quot;&quot;)
    .split(&quot;#&quot;)[0]
    .split(&quot;?&quot;)[0];

  const outputFolder = path.join(targetFolder, target);

  mkdirp.sync(outputFolder);

  fs.writeFileSync(
    path.join(outputFolder, `${id}.json`),
    JSON.stringify(mention, null, 2),
    { encoding: &quot;utf-8&quot; },
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;With data stored per article path, &lt;code&gt;src/components/Webmentions.astro&lt;/code&gt; can query the &lt;code&gt;webmentions&lt;/code&gt; collection by &lt;code&gt;slug&lt;/code&gt; to load only the relevant mentions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const { slug } = Astro.props;


const webmentions = await getCollection(&quot;webmentions&quot;, ({ id }) =&amp;gt; {
  return Boolean(id.includes(slug));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Automating the Script with GitHub Actions&lt;/h2&gt;
&lt;p&gt;To keep Webmention data current without manually re-running the script, a GitHub Action runs on a schedule to fetch new mentions, writing each mention to its own JSON file, and opening a pull request with the changes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/github-automated-webmention-pr.png&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;Example PR opened by GitHub Action that contains JSON associated with 11 new Webmentions&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The Action lives at &lt;code&gt;.github/workflows/webmentions.yml&lt;/code&gt; and runs on a cron schedule every 2 hours. A future refinement could trigger only on new Webmentions. Webmention.io supports webhooks which could be configured to send a POST request each time a verified mention is received.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;on:
  workflow_dispatch:
  schedule:
    - cron: &quot;0 0/2 * * *&quot; # every 2 hours
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;info&quot;&amp;gt;
&lt;a href=&quot;https://crontab.cronhub.io/&quot;&gt;Crontab&lt;/a&gt; is a handy resource for writing out cron expressions.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;The first few steps of the GitHub Action is installing dependencies and creating an environment variable for the Webmentions script to access.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; steps:
      - name: Check out repository
        uses: actions/checkout@v4
      - name: Create .env file
        run: |
          echo &quot;WEBMENTION_API_KEY: ${{ secrets.WEBMENTION_API_KEY }}&quot; &amp;gt;&amp;gt; .env
      - name: Set up Node.js
        uses: actions/setup-node@master

      - name: Install dependencies
        run: npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With dependencies in place, the Action runs the fetch script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; - name: Fetch webmentions
        env:
          WEBMENTION_API_KEY: ${{ secrets.WEBMENTION_API_KEY }}
        run: node ./automations/store-webmentions.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the new Webmention files are saved, the Action opens a pull request with the changes for review:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Create Pull Request
        id: cpr
        uses: peter-evans/create-pull-request@v7
        with:
          token: ${{ secrets.GITHUB_TOKEN}}
          commit-message: Update WebMentions
          assignees: m0nica
          title: Update Webmentions
          body: Update Webmentions
          branch: fetch-webmentions
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;View full GitHub Actions Script to Fetch and Save Webmentions&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: Webmentions

on:
  workflow_dispatch:
  schedule:
    - cron: &quot;0 0/2 * * *&quot; # every 2 hours

jobs:
  webmentions:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v4
      - name: Create .env file
        run: |
          echo &quot;WEBMENTION_API_KEY: ${{ secrets.WEBMENTION_API_KEY }}&quot; &amp;gt;&amp;gt; .env
      - name: Set up Node.js
        uses: actions/setup-node@master

      - name: Install dependencies
        run: npm install

      - name: Fetch webmentions
        env:
          WEBMENTION_API_KEY: ${{ secrets.WEBMENTION_API_KEY }}
        run: node ./automations/store-webmentions.js

      - name: Create Pull Request
        id: cpr
        uses: peter-evans/create-pull-request@v7
        with:
          token: ${{ secrets.GITHUB_TOKEN}}
          commit-message: Update WebMentions
          assignees: m0nica
          title: Update Webmentions
          body: Update Webmentions
          branch: fetch-webmentions

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;h2&gt;Future Ideas&lt;/h2&gt;
&lt;h3&gt;Automating Outgoing Webmentions&lt;/h3&gt;
&lt;p&gt;The next piece is automating outgoing Webmentions when new content is published. The &lt;a href=&quot;https://github.com/remy/wm/tree/master&quot;&gt;&lt;code&gt;wm&lt;/code&gt; CLI tool&lt;/a&gt; (&lt;code&gt;@remy/webmention&lt;/code&gt;) makes this straightforward: pass in an RSS feed and it handles discovery and delivery.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx webmention https://yoursite.com/feed.xml --limit 1 --send
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sophie Koonin covers how to &lt;a href=&quot;https://localghost.dev/blog/sending-webmentions-from-a-static-site/&quot;&gt;wire this into a &lt;code&gt;postbuild&lt;/code&gt; script&lt;/a&gt; so it re-runs automatically on every rebuild. However, since this site is written in MDX its RSS feed only contains excerpts as opposed to the full article. Whereas, the outgoing Webmention tool needs full content to find links worth mentioning. Jason Lengstorf documented a workaround in &lt;a href=&quot;https://codetv.dev/blog/mdx-to-rss-astro&quot;&gt;The terrible things I did to Astro to render MDX content in my RSS feed&lt;/a&gt; to provide more robust MDX support in RSS.&lt;/p&gt;
&lt;h3&gt;AI-Assisted PR Review&lt;/h3&gt;
&lt;p&gt;An AI moderation step would also improve the incoming side. The fetch-and-store script is responsible for retrieving the data as-is however, The PR review requires reviewing a batch of new JSON files, filtering for potential spam, and summarizing what arrived is the type of task a language model handles well with human supervision.&lt;/p&gt;
&lt;p&gt;If you&apos;ve solved the MDX-to-RSS problem in Astro or have thoughts on the AI moderation approach, feel free to reach out.&lt;/p&gt;
&lt;p&gt;[^1]: &quot;Webmention is an open web standard (W3C Recommendation) for conversations and interactions across the web, a powerful building block used for a growing distributed network of peer-to-peer comments, likes, reposts, and other responses across the web&quot; — &lt;a href=&quot;https://indieweb.org/Webmention&quot;&gt;IndieWeb Wiki&lt;/a&gt;.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Delete Your Code and Other Reflections from Coderetreat Day</title><link>https://aboutmonica.com/blog/code-retreat-reflection/</link><guid isPermaLink="true">https://aboutmonica.com/blog/code-retreat-reflection/</guid><description>Some of my reflections from participating in the 2019 Global Coderetreat Day where I solved Conway&apos;s Game of Life various ways with different people and principles each time.</description><pubDate>Sun, 17 Nov 2019 02:43:13 GMT</pubDate><content:encoded>&lt;p&gt;import { Tweet } from &quot;@astro-community/astro-embed-twitter&quot;;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/conways-game-of-life.gif#center&quot; alt=&quot;trefoil knot version of conway&apos;s game of life&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Trefoil Knot Conway&apos;s Game of Life, Source: &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Trefoil_knot_conways_game_of_life.gif&quot;&gt;Raphaelaugusto&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I participated in the 2019 &lt;a href=&quot;https://www.coderetreat.org/&quot;&gt;Global Coderetreat Day&lt;/a&gt; which was held on November, 16th, 2019, and attempted to solve &quot;&lt;a href=&quot;https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life&quot;&gt;Conway&apos;s Game of Life&lt;/a&gt;&quot; five times throughout the day. Each time I went through the problem with different people and focused on a different software development approach such as Test-Driven Development (TDD). TDD is a software development practice where individual unit tests are written before writing any functional logic and the specific tests dictate which functionality should be implemented next. By the end of the day, I had used various code editors including Visual Studio Code, CodeSandbox, Glitch, and Atom as well as different flavors of JavaScript and JavaScript testing.&lt;/p&gt;
&lt;p&gt;&amp;lt;Tweet tweetId=&quot;https://twitter.com/coderetreat/status/1088318474487848961?s&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;What is Global Coderetreat Day?&lt;/h2&gt;
&lt;p&gt;Global Coderetreat Day is a specific day where volunteers across the world organize and facilitate Coderetreats. A Coderetreat is a day-long event where participants solve Conway&apos;s Game of Life multiple times throughout the day alongside another developer. The practice of solving a problem with another engineer is known as pair programming. The emphasis of a Coderetreat is on practicing fundamental software development practices without worrying about finishing a problem and events can either be language-agnostic or focus on a specific programming language. Traditionally, the problem solved is Conway&apos;s Game of Life but some Coderetreats may choose to focus on a different problem.&lt;/p&gt;
&lt;h3&gt;What is Conway&apos;s Game of Life?&lt;/h3&gt;
&lt;p&gt;Game of Life is a simulation in which there&apos;s a two-dimensional grid of cells where each cell is either alive or dead. The fate of each cell is dependent upon the state of its (up to 8) neighbors. Neighbors are considered to be cells that are horizontally, vertically, or diagonally adjacent.&lt;/p&gt;
&lt;h4&gt;The Simplified Rules of Conway&apos;s Game Of Life (source: Wikipedia)&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Any live cell with two or three neighbors survives.&lt;/li&gt;
&lt;li&gt;Any dead cell with three live neighbors becomes a live cell.&lt;/li&gt;
&lt;li&gt;All other live cells die in the next generation. Similarly, all other dead cells stay dead.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;Below is an illustration from the &lt;a href=&quot;https://www.coderetreat.org/pages/facilitating/gol/&quot;&gt;Coderetreat facilitation guide&lt;/a&gt; that shows how the above Game of Life rules play out to determine what state a cell will transition to. In particular, we are looking at the center cell (which would have the index &lt;code&gt;[1][1]&lt;/code&gt; if this grid were represented as a multi-dimensional array). In the illustration, the live cells have a smiley face whereas the dead cells have the absence of an emoji and the blue emoji with the tongue stuck out indicates that the smiley is transitioning into being dead based on its current state and the number of live neighbors that it has.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/game-of-life-smileys.png#center&quot; alt=&quot;smiley representation of the game of life&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Each time you attempt to solve Conway&apos;s Game of Life during a Coderetreat it is required that you start with a clean slate and get rid of all of your previous work and come ready to approach the problem from a different angle. Starting over can feel especially frustrating right after the first iteration in my opinion as a good amount of time can go into getting oriented with Conway&apos;s Game of Life, the premise of a Coderetreat and setting up the workspace to be productive. However, the beauty of having to completely delete the code in each round means that instead of relying on previous solutions (or mistakes) each iteration forces active recall vs passively referencing previous code. &quot;Starting over&quot; also leaves room for more innovation and exploration than just building on the same solution directly throughout the day would.&lt;/p&gt;
&lt;h2&gt;The Format of the Coderetreat&lt;/h2&gt;
&lt;p&gt;In the version of Coderetreat that I participated in, there were 5 iterations, which were ~40 mins. chunks of time to attempt
to solve the problem. For each iteration, we were provided with different constraints, deleted all of our code and switched to pair with someone new. Each of these iterations provided me with the opportunity to address the problem from a different angle as I worked through the problem with someone different than in the previous iterations and with a new mental framework in mind. The format for each iteration varied as follows&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;focus on getting familiar with Conway&apos;s Game of Life and attempt our first solution&lt;/li&gt;
&lt;li&gt;solve the problem with ping-pong pair programming&lt;/li&gt;
&lt;li&gt;solve the problem with red-green refactoring&lt;/li&gt;
&lt;li&gt;solve the problem focusing on very clear naming conventions&lt;/li&gt;
&lt;li&gt;solve the problem by writing out all of the tests first&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After each iteration we also had a mini-retro to discuss how the previous iteration went and how we felt about a particular approach.&lt;/p&gt;
&lt;h3&gt;Getting familiar with Conway&apos;s Game of Life, Coderetreat and greenfield development&lt;/h3&gt;
&lt;p&gt;The first iteration of Coderetreat was a bit challenging for me as I needed to collaborate with people I hadn&apos;t worked with before to quickly figure out how to get an environment up and running in a language with a testing framework. There was a lot of grunt work that needed to be completed before even starting to focus on solving the problem with code. Some folks chose to spend the first iteration getting more familiar with Conway&apos;s Game of Life versus setting up their environment. However, having some challenges while setting up a testing library from scratch and configuring the project to allow ES6 syntax during the initial iteration helped make later iterations a lot more efficient. Although in each iteration I was &quot;starting over&quot; from a code-perspective, after the initial one I was able to spend more time focusing on writing code and tests for the problem at hand and less time setting up tools.&lt;/p&gt;
&lt;h3&gt;Ping-Pong Pair Programming Approach&lt;/h3&gt;
&lt;p&gt;In particular, I enjoyed the ping-pong pair programming iterations as it lent itself to being a very structured way to approach the task at hand in a focused way and everyone had a clear role throughout the iteration. The ping-pong part of this approach involved one person writing an initial unit test to satisfy a requirement of Conway&apos;s Game of Life and then the other person worked to write code that made the unit test pass. Then the person who wrote functioning code writes another unit test that should be solved by the other person and then going back and forth writing tests and solving them until the iteration was over.&lt;/p&gt;
&lt;h3&gt;Red, Green, Refactor and Mob Programming Approach&lt;/h3&gt;
&lt;p&gt;I also enjoyed the red, green, refactoring approach in which we went through solving Conway&apos;s Game of Life first by writing a failing test (red), then writing code that solved the failing test (green) and then refactoring to improve the initial working solution without making the initial tests fail. During the initial phase when trying to get the test to pass we were instructed to write the &quot;dumbest&quot; code that would allow the test to pass even if that involved hard-coding the functionality. Also, in this particular iteration where the emphasis was on red, green, refactoring I worked in a mob (a group of three developers) where we had three distinct roles. A driver, observer, and navigator. The driver was responsible for actually typing code based on the navigator&apos;s instructions while the observer had a less perscriptive role. We decided that every 20 mins. the driver would switch and every 10 mins. the observer and navigator switched off to allow everyone to serve various roles. Like the ping-pong programming iteration, I appreciated having discrete roles for everyone for a set duration.&lt;/p&gt;
&lt;h3&gt;Naming Conventions Approach&lt;/h3&gt;
&lt;p&gt;The iteration which focused on programming with more clear naming conventions allowed me and my partner to focus on communicating more to ensure we were on the same page regarding the purpose of particular variables and methods. The approach of focusing on naming conventions led to me breaking down my approach to writing Conway&apos;s Game of Life into into smaller pieces which ultimately led to us discussing when and how to consolidate these highly descriptive functions. In other iterations that did not take this approach I was more likey to write more ambiguous function that handled significantly more logic.&lt;/p&gt;
&lt;h3&gt;Writing Test Cases Upfront Approach&lt;/h3&gt;
&lt;p&gt;The last iteration of the day involved coming up with a list of various tests upfront (within a few minutes) which I think helped ensure the solution we implemented would satisfy a large amount of test coverage. This was a good starting point and I could see the approach of deciding test cases upfront being very helpful if more time is spent writing out meaningful tests.&lt;/p&gt;
&lt;p&gt;Above were the approaches we focused on during this particular Coderetreat but countless approaches or constraints could have been introduced like attempting to solve the problem without using any loops or without using conditionals.&lt;/p&gt;
&lt;h2&gt;Takeaways&lt;/h2&gt;
&lt;p&gt;My takeaways from Coderetreat, despite working through Conway&apos;s Game of Life numerous times weren&apos;t how to program Conway&apos;s Game of Life from end to end or even the most efficient way to approach programming. The key takeaways for me were more-so centered around how there a various approaches that can be taken when developing software and how different approaches shift my thinking, leading to different solutions. I ended up using JavaScript for each iteration and was able to see various ways that the same problem could be approached within one programming language e.g., from a more class-based or functional approach. I do not doubt that I barely scratched the surface on the types of approaches that could&apos;ve been taken but I enjoyed the exploration.&lt;/p&gt;
&lt;p&gt;I learned to get more comfortable deleting my code (without committing to git) since we had to destroy all remnants of our code between each iteration. The intent of this was to force us to approach each iteration with fresher eyes,ready to try a completely different approach and reinforce learnings from one iteration to another through active recall. I may want to incorporate this into my workflow by spending more time exploring various solutions to a problem and being okay doing a &lt;code&gt;git reset&lt;/code&gt; or &lt;code&gt;git stash&lt;/code&gt; and then trying a different approach.&lt;/p&gt;
&lt;p&gt;I was challenged throughout the day to embrace the TDD principle of writing the simplest thing to make a test pass even if that meant the solution was hard-coded. Eventually you will hit a wall if all of the logic to satisfy unit tests is hardcoded vs more dynamic however this approach tries to prevent people from adding premature logic before it is required. The idea with this approach to TDD is solutions should evolve to be more sophisticated, as additional tests are added and introduce new requirements that cause breaking changes to more simplistic logic.&lt;/p&gt;
&lt;p&gt;A strict TDD approach where you write the &quot;dumbest&quot; thing possible to pass a particular test can lead to writing functioning code that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# my_first_calculator.py by AceLewis modified by Monica Powell

print(&apos;Welcome to this calculator!&apos;)
print(&apos;It can add whole numbers from 0 to 10&apos;)
num1 = int(input(&apos;Please choose your first number: &apos;))
num2 = int(input(&apos;Please choose your second number: &apos;))

if num1 == 0 and num2 == 0:
    print(&quot;0+0 = 0&quot;)
if num1 == 0 and num2 == 1:
    print(&quot;0+1 = 1&quot;)
if num1 == 0 and num2 == 2:
    print(&quot;0+2 = 2&quot;)
if num1 == 0 and num2 == 3:
    print(&quot;0+3 = 3&quot;)
if num1 == 0 and num2 == 4:
    print(&quot;0+4 = 4&quot;)
if num1 == 0 and num2 == 5:
    print(&quot;0+5 = 5&quot;)
if num1 == 0 and num2 == 6:
    print(&quot;0+6 = 6&quot;)
if num1 == 0 and num2 == 7:
    print(&quot;0+7 = 7&quot;)
if num1 == 0 and num2 == 8:
    print(&quot;0+8 = 8&quot;)
if num1 == 0 and num2 == 9:
    print(&quot;0+9 = 9&quot;)
if num1 == 0 and num2 == 10:
    print(&quot;0+10 = 10&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above code is calculator function written in Python that takes in two numbers and adds them. While this particular calculator would pass tests up until a limit because all of the logic is hard-coded it will not know how to add numbers that are outside of its scope. The &lt;a href=&quot;https://github.com/AceLewis/my_first_calculator.py/blob/master/my_first_calculator.py&quot;&gt;full version of this code&lt;/a&gt; on GitHub can add, subtract, multiply and divide whole numbers from 0 to 50, and is written a similar fashion to the sample above, however the author indicated that handling higher numbers led to Python crashing as the program isn&apos;t written in an efficient way. Therefore an important part of TDD is deciding when it makes sense to spend time optimizing a solution to pass several tests versus just hard-coding one&apos;s way through the problem.&lt;/p&gt;
&lt;p&gt;Thank you Stride Consulting and App Academy for hosting the 2019 New York City Global Coderetreat Day. I enjoyed learning more about various software development practices and having the opportunity to workly closely with a handful of different people!&lt;/p&gt;
&lt;p&gt;If you&apos;re interested in organizing your own Coderetreat &lt;a href=&quot;https://www.coderetreat.org/&quot;&gt;their official website&lt;/a&gt; has plenty of resources to help you get started and if you would like to learn more about some of the principles I wrote about above, Corey Haines, one of the founders of the Global Coderetreat Day wrote a book &lt;a href=&quot;https://leanpub.com/4rulesofsimpledesign/c/gdcr&quot;&gt;&quot;Understanding the Four Rules of Simple Design&quot;&lt;/a&gt; that details his observations and learnings from watching thousands of pairs work through solving Conway&apos;s Game of Life.&lt;/p&gt;
&lt;p&gt;&amp;lt;Tweet tweetLink=&quot;cellular_bot/status/1195686985144373248?s&quot; /&amp;gt;&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Conquering the Command Line</title><link>https://aboutmonica.com/blog/conquering-the-command-line/</link><guid isPermaLink="true">https://aboutmonica.com/blog/conquering-the-command-line/</guid><description>When I was first introduced to the command line I really had to adjust to navigating my computer in a black box with just text. So I avoided the command line as much as possible. I was accustomed to…</description><pubDate>Tue, 05 Dec 2017 17:11:11 GMT</pubDate><content:encoded>&lt;h4&gt;A brief guide to getting started on UNIX/Mac OS terminal&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/media/conquering-the-command-line-0.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Output on Mac OS terminal after typing: &lt;strong&gt;telnet towel.blinkenlights.nl&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When I was first introduced to the command line I really had to adjust to navigating my computer in a black box with just text. So I avoided the command line as much as possible. I was accustomed to the visual cues and feedback that a computer usually provides. In many ways it felt like I was re-learning how to use a computer via the command line.&lt;/p&gt;
&lt;p&gt;Yet, since first learning how to navigate my computer using UNIX commands I’ve learned that the command line doesn’t have to be a scary thing just because there’s no visual feedback when typing a password in on the command line. As security, nothing shows up as you type in your password to indicate that any characters have been entered.&lt;/p&gt;
&lt;h4&gt;What is the command line?&lt;/h4&gt;
&lt;p&gt;The command line is a software that executes commands or instructions for a computer to manipulate or interact with its file system.&lt;/p&gt;
&lt;h3&gt;What is UNIX?&lt;/h3&gt;
&lt;h4&gt;Why Use the Command Line?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Faster to modify, navigate between files&lt;/li&gt;
&lt;li&gt;Able to install software as a superuser&lt;/li&gt;
&lt;li&gt;Can see hidden dotfiles&lt;br /&gt;
dotfiles are UNIX configuration files, they tend to be files that are proceeded with a &lt;code&gt;.&lt;/code&gt; and are hidden to normal users.&lt;br /&gt;
You can &lt;a href=&quot;https://medium.com/@webprolific/getting-started-with-dotfiles-43c3602fd789&quot;&gt;learn more about getting started with dotfiles in this article&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In order to get started on the command line you should navigate to your applications and open the &lt;strong&gt;Terminal&lt;/strong&gt; application.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/conquering-the-command-line-1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Above is the Terminal Icon on Mac.&lt;/p&gt;
&lt;h3&gt;Create a Basic Website Folder on the Command Line&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/media/conquering-the-command-line-2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Folder structure of sample project&lt;/p&gt;
&lt;p&gt;A folder with the above structure can be create on the command line by typing the commands inside of an empty directory:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/conquering-the-command-line-3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We start inside of an empty directory!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make a directory (also known as a folder) called personal-website&lt;br /&gt;
&lt;code&gt;mkdir personal-website&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/media/conquering-the-command-line-4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We’ve created a folder named personal-website&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Navigate to inside of the directory called personal-website&lt;br /&gt;
&lt;code&gt;cd personal-website&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;create a directory, inside of the personal-website folder called assets&lt;br /&gt;
&lt;code&gt;mkdir assets&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/media/conquering-the-command-line-5.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We’ve created a folder inside of personal-website to contain all of our assets&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Navigate inside of the assets folder which is inside of the personal-website folder&lt;br /&gt;
&lt;code&gt;cd assets&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;create a directory, inside of the assets folder named images&lt;br /&gt;
&lt;code&gt;mdkir images&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;create a directory, inside of the assets folder named js&lt;br /&gt;
&lt;code&gt;mkdir js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;create a directory, inside of the assets folder named css&lt;br /&gt;
&lt;code&gt;mkdir css&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/media/conquering-the-command-line-6.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We’ve created folders inside of personal-website/assets to store our project’s assets&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/conquering-the-command-line-7.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Woops! We forgot to create an index.html file :(&lt;/p&gt;
&lt;p&gt;We are in the assets folder and want an index.html file in our main personal-website folder. Typing &lt;code&gt;cd ..&lt;/code&gt; will move us out of the assets folder and into the directory above which is personal-website. Now that we are in the personal-website folder if we type &lt;code&gt;touch index.html&lt;/code&gt; a blank index.html file will be created.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/conquering-the-command-line-8.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Some frequently used terminal commands are:&lt;/h3&gt;
&lt;h4&gt;commands to navigate/manipulate the filesystem&lt;/h4&gt;
&lt;p&gt;**ls&lt;br /&gt;
****list** the contents of a directory&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;pwd&lt;br /&gt;
print working directory&lt;/strong&gt; for the terminal to display the directory you are currently working on&lt;/p&gt;
&lt;p&gt;**touch &lt;br /&gt;
**create or open a file without making any changes&lt;br /&gt;
very handy when wanting to create empty files without leaving the command line&lt;/p&gt;
&lt;p&gt;**sudo &lt;br /&gt;
**this allows you to run commands as a &lt;strong&gt;super user&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mv &lt;br /&gt;
move&lt;/strong&gt; a file or directorythis can be used to move or rename a file by updating the file path&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;cd&lt;/strong&gt; &lt;br /&gt;
&lt;strong&gt;change the current directory&lt;/strong&gt; you are working on so that you can access files on a different part of the system&lt;br /&gt;
&lt;code&gt;cd&lt;/code&gt; moves you to the root directory (top level folder on computer — usually the current User)&lt;br /&gt;
&lt;code&gt;cd .&lt;/code&gt; current directory &lt;br /&gt;
&lt;code&gt;cd ..&lt;/code&gt; navigates to directory two levels up&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mkdir&lt;/strong&gt; &lt;br /&gt;
&lt;strong&gt;make&lt;/strong&gt; a new &lt;strong&gt;directory&lt;/strong&gt; (or a folder)&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;Commands to Install Software&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;You can install some software from the command line using the following commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;in Python &lt;code&gt;pip install &amp;lt;package name&amp;gt;.&lt;/code&gt; &lt;br /&gt;
Pip is a software package manager for Python.&lt;/li&gt;
&lt;li&gt;in JavaScript &lt;code&gt;npm install &amp;lt;package name&amp;gt;&lt;/code&gt; &lt;br /&gt;
NPM is a package manager for JavaScript pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Commands to Run Software&lt;/h4&gt;
&lt;p&gt;In order to run a script on the command line you need to provide a command prompt and file name. Some examples are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;in Java &lt;code&gt;javac filename.java&lt;/code&gt; and then &lt;code&gt;java filename&lt;/code&gt; compiles java projects and then runs them.&lt;/li&gt;
&lt;li&gt;in Python &lt;code&gt;python filename&lt;/code&gt; runs python scripts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you find you are repeating a lot of commands you can scroll through your recent commands using the up/down arrows and edit them and re-run by navigating to them and then pressing enter.&lt;/p&gt;
&lt;h4&gt;Additional Resources to Get Started with Command Line Prompts&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://web.mit.edu/mprat/Public/web/Terminus/Web/main.html&quot;&gt;MIT Terminus (interactive game to learn command line)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.codecademy.com/learn/learn-the-command-line&quot;&gt;Codecademy Learn the Command Line&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learnpythonthehardway.org/book/appendixa.html&quot;&gt;Learn Python the Hard Way’s Command Line Crash Course&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Decorating the Command Line&lt;/h4&gt;
&lt;p&gt;You can completely customize the colors and outputs on the command line to better suit your visual and aesthetic needs.&lt;/p&gt;
&lt;p&gt;Here’s how I’ve made my command line prettier :&lt;/p&gt;
&lt;p&gt;How to install Tomorrow Night&lt;br /&gt;
&lt;a href=&quot;https://github.com/chriskempson/tomorrow-theme/blob/master/OS%20X%20Terminal/Tomorrow%20Night.terminal&quot;&gt;https://github.com/chriskempson/tomorrow-theme/blob/master/OS%20X%20Terminal/Tomorrow%20Night.terminal&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://mindthecode.com/customize-the-terminal/&quot;&gt;&lt;strong&gt;Customize the terminal&lt;/strong&gt;&lt;br /&gt;
_I love the terminal. Besides the fact it makes you look awesome while using it, it can also do about a gazillion…_mindthecode.com&lt;/a&gt;&lt;a href=&quot;https://mindthecode.com/customize-the-terminal/&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you enjoyed reading this article consider tapping the clap button 👏. Wanna see more of my work? Check out&lt;/em&gt; &lt;a href=&quot;https://github.com/M0nica/&quot;&gt;&lt;em&gt;my GitHub&lt;/em&gt;&lt;/a&gt; &lt;em&gt;to view my code and learn more about my development experience at&lt;/em&gt; &lt;a href=&quot;http://aboutmonica.com&quot;&gt;&lt;em&gt;http://aboutmonica.com&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Gradients with Lerp</title><link>https://aboutmonica.com/blog/crafting-color-gradients-with-lerp/</link><guid isPermaLink="true">https://aboutmonica.com/blog/crafting-color-gradients-with-lerp/</guid><description>This tutorial walks through how linear interpolation (or lerp)  can be used to automagically generate gradients</description><pubDate>Thu, 05 Sep 2024 13:12:21 GMT</pubDate><content:encoded>&lt;p&gt;import P5Sandpack from &quot;../../components/P5CustomSandpack.tsx&quot;
import CodePen from &quot;../../components/Pen.astro&quot;;
import cliffordAttractor from &quot;./assets/clifford-attractor.png&quot;;
import gradientGrid from &quot;../sketches/gradient-grid/preview.png&quot;;
import lerpNumbers from &quot;./assets/lerp-numbers.png&quot;;
import purplePinkGradient from &quot;./assets/purple-pink-gradient.png&quot;;
import P5ExternalLink from &quot;../../components/p5ExternalLink.astro&quot;;
import HexColor from &quot;../../components/hexColor.astro&quot;;
import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;Creative coding has given me a better appreciation for how math can be used to generate visuals, like this &lt;em&gt;donut-shaped&lt;/em&gt; &amp;lt;a href=&quot;https://paulbourke.net/fractals/clifford/&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&amp;gt;Clifford Attractor&amp;lt;/a&amp;gt; I created earlier this year during &amp;lt;a href=&quot;https://genuary.art/&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&amp;gt;#genuary&amp;lt;/a&amp;gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;img src={cliffordAttractor.src} /&amp;gt;&lt;/p&gt;
&lt;p&gt;This article focuses on &amp;lt;a href=&apos;https://p5js.org/reference/p5/lerpColor/&apos; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&amp;gt;&lt;code&gt;lerpColor()&lt;/code&gt;&amp;lt;/a&amp;gt; which is one of my favorite creative coding techniques and p5⁎js
functions. It uses math to precisely compute the blended output of two colors.&lt;/p&gt;
&lt;h2&gt;What is Linear Interpolation?&lt;/h2&gt;
&lt;p&gt;Linear interpolation is a mathematical method used to &quot;fill in the gaps&quot; between two known data points. A straight line known as &quot;linear interpolant&quot; is created between the two data points and when given a desired position between the two that position is mapped to a value based on where it occurs in the straight line.&lt;/p&gt;
&lt;p&gt;&amp;lt;img src={purplePinkGradient.src} /&amp;gt;&lt;/p&gt;
&lt;p&gt;The above gradient is composed of ~200 rectangles that all have slightly different fill colors that seamlessly create a gradient when they&apos;re aligned. Explore the associated p5⁎js code and play around with the gradient below 👇🏾:&lt;/p&gt;
&lt;p&gt;&amp;lt;P5Sandpack
editorWidthPercentage={50}
sketchJS={`
const colors = [
&quot;#ffadad&quot;,
&quot;#ffd6a5&quot;,
&quot;#fdffb6&quot;,
&quot;#caffbf&quot;,
&quot;#9bf6ff&quot;,
&quot;#a0c4ff&quot;,
&quot;#bdb2ff&quot;,
&quot;#ffc6ff&quot;
];&lt;/p&gt;
&lt;p&gt;let from;
let to;
let displayHelperText = true;&lt;/p&gt;
&lt;p&gt;function setup() {
colorMode(RGB);
createCanvas(windowWidth, windowHeight);
noStroke();
fill(&quot;#FFF&quot;);
textStyle(BOLD);
textSize(20);
from = color(random(colors));
to = color(random(colors));
}&lt;/p&gt;
&lt;p&gt;//
function draw() {
// clear canvas of elements from previous draw
clear();&lt;/p&gt;
&lt;p&gt;let values = 200;
let count = 0;&lt;/p&gt;
&lt;p&gt;while (count &amp;lt; values) {
let amount = count / values;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let x = (width / values) * count;
fill(lerpColor(from, to, amount));

rect(x, 0, 10, height);

count++;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;if (displayHelperText) {
textSize(windowWidth / 25);
fill(&quot;#2a2135&quot;);
textAlign(CENTER);&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;text(&quot;Click Spacebar To Regenerate&quot;, width / 2, height / 2);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}
}&lt;/p&gt;
&lt;p&gt;function keyPressed() {
const SPACEBAR = &quot; &quot;;
// pause/play animation when spacebar is pressed for sketches that animate from draw to draw
if (key == SPACEBAR) {
from = color(random(colors));
to = color(random(colors));
displayHelperText = false;
}&lt;/p&gt;
&lt;p&gt;if (key === &quot;s&quot;) {
saveCanvas(&quot;gradient&quot;);
}
}&lt;/p&gt;
&lt;p&gt;function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}&lt;/p&gt;
&lt;p&gt;`}
client:only=&quot;react&quot;
/&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;h2&gt;Computing color based on position&lt;/h2&gt;
&lt;p&gt;The same approach of computing the fill color of shapes based on the &lt;code&gt;x&lt;/code&gt; or &lt;code&gt;column&lt;/code&gt; position is applied in the below &amp;lt;a href=&quot;/sketches/gradient-grid&quot;&amp;gt;gradient grid&amp;lt;/a&amp;gt; of irregular &quot;squares&quot; which uses &lt;code&gt;lerpColor()&lt;/code&gt; to transition the colors from &amp;lt;HexColor color=&quot;#9f86c0&quot;/&amp;gt; to &amp;lt;HexColor color=&quot;#e0b1cb&quot;/&amp;gt; based on the position of the current shape (computed by &lt;code&gt;column/height&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&quot;/sketches/gradient-grid&quot;&amp;gt;
{&quot; &quot;}
&amp;lt;img src={gradientGrid.src} /&amp;gt;
&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// parameters for lerpColor
lerpColor(c1, c2, amt);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The p5⁎js &lt;code&gt;lerpColor()&lt;/code&gt; requires 3 parameters, &lt;code&gt;c1&lt;/code&gt; which is the color it interpolates from , &lt;code&gt;c2&lt;/code&gt; which is the color it interpolates to and &lt;code&gt;amt&lt;/code&gt; which should be a number between 0 and 1.&lt;/p&gt;
&lt;p&gt;The general algorithm used to create the color distribution of the shapes based on their column:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;loop through each the x, y position of each element&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;set fill color using lerpColor based on the current element&apos;s column&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;draw shape to represent element&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, let&apos;s translate that into p5⁎js code.&lt;/p&gt;
&lt;p&gt;The below code shows how we can compute the shape&apos;s fill color value between &amp;lt;HexColor color=&quot;#9f86c0&quot;/&amp;gt; and &amp;lt;HexColor color=&quot;#e0b1cb&quot;/&amp;gt; based on its position.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const from = color(&quot;#9f86c0&quot;); // c1
const to = color(&quot;#e0b1cb&quot;); // c2
const amt = column / height;

// changes fill color based
// on the position of the element

fill(lerpColor(from, to, amt));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the below example, the value of &lt;code&gt;column/height&lt;/code&gt; varies from 0.206 to 1.646 in this particular grid and we see different color values for each column as they each has a distinct &lt;code&gt;column/height&lt;/code&gt; value and therefore maps to a different color value between &amp;lt;HexColor color=&quot;#9f86c0&quot;/&amp;gt; and &amp;lt;HexColor color=&quot;#e0b1cb&quot;/&amp;gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;img src={lerpNumbers.src} /&amp;gt;&lt;/p&gt;
&lt;p&gt;However, you may notice the latter half of the grid has 4 columns that appear to be the same color.
This is due to the fact that the computed value of the &lt;code&gt;column/height&lt;/code&gt; is greater than 1 and when
you pass in a value less than 0 or greater than 1 then &lt;code&gt;lerpColor()&lt;/code&gt; will normalize the value to the closest number between 0 and 1 in order to minimize strange results with the output color. This behavior is distinct from the similar &lt;code&gt;lerp()&lt;/code&gt; function which is designed to be used with numbers and will compute values that are out of range of from it&apos;s numerical &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;stop&lt;/code&gt; parameters.&lt;/p&gt;
&lt;p&gt;Explore the associated p5⁎js code and play around with the grid of shapes below 👇🏾:&lt;/p&gt;
&lt;p&gt;&amp;lt;P5Sandpack
editorWidthPercentage={50}
sketchJS={`
function setup() {
createCanvas(windowWidth, windowHeight);
background(&quot;#faf4fb&quot;);
noStroke();
}&lt;/p&gt;
&lt;p&gt;function draw() {
noLoop();
background(&quot;#faf4fb&quot;);
let sz = 45;
let numOfColumns = windowWidth / 12;
let numOfRows = windowHeight / 12;
let margin = .25;&lt;/p&gt;
&lt;p&gt;const from = color(&quot;#9f86c0&quot;)&lt;/p&gt;
&lt;p&gt;const to = color(&quot;#e0b1cb&quot;)
let spacing = 50;
for (let row =spacing; row &amp;lt; windowWidth - spacing ; row+= spacing) {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for (let column = spacing; column &amp;lt;windowHeight - spacing; column+=spacing) {

  const idx = Math.floor(map(row, 0, (windowWidth-spacing), 0, 2));
 

  fill(lerpColor(from, to, row/height))

  square(
    row,
   column,
    sz,
     random(5, 75),
    random(5, 75),
    random(5, 75),
    random(5, 75)
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}
}&lt;/p&gt;
&lt;p&gt;function mousePressed() {
redraw();
}&lt;/p&gt;
&lt;p&gt;function keyPressed() {
const SPACEBAR = &quot; &quot;;
if (key == SPACEBAR || key == &quot;r&quot;) {
redraw();
}&lt;/p&gt;
&lt;p&gt;if (key === &quot;s&quot;) {
saveCanvas(&quot;random-shapes-grid&quot;);
}
}&lt;/p&gt;
&lt;p&gt;function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}&lt;/p&gt;
&lt;p&gt;`}
client:only=&quot;react&quot;
/&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Thanks for reading this article! I hope you learned something new about how linear interpolation can be applied to generating colors. I&apos;d love to see what you create with &lt;code&gt;lerpColor()&lt;/code&gt;. Feel free to share with me on &amp;lt;a href=&quot;https://x.com/indigitalcolor&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&amp;gt;Twitter&amp;lt;/a&amp;gt;.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>How to Add Search Functionality to a Gatsby Blog</title><link>https://aboutmonica.com/blog/create-gatsby-blog-search-tutorial/</link><guid isPermaLink="true">https://aboutmonica.com/blog/create-gatsby-blog-search-tutorial/</guid><description>This tutorial will walk through how to create functionality to allow users to filter posts on a Gatsby site by description, title and, tags. </description><pubDate>Tue, 26 Nov 2019 02:43:13 GMT</pubDate><content:encoded>&lt;p&gt;I recently added functionality to this site to allow visitors to filter posts based on the posts description, title, and tags in an effort to allow better discovery of content. This tutorial will is based off of how I implemented a basic search on this site and will cover how to create a search filter on a site built with &lt;a href=&quot;https://www.gatsbyjs.org&quot;&gt;GatsbyJS&lt;/a&gt;. In particular, this tutorial walks through how to create an input field that allows users to filter a list of an entire Gatsby site&apos;s posts if the description, title or tags matches the input query. The solution proposed in this tutorial leverages GraphQL and React hooks to update the state to show appropriate data when content is filtered.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;lt;center&amp;gt;Demo of the Search Filter&amp;lt;/center&amp;gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/filter-demo.gif#center-small&quot; alt=&quot;filter demo from aboutmonica.com/writing&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;h3&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;Although, some of the implementation details can be abstracted and applied in any React application to get the most value out of this tutorial you should have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Some knowledge of ES6 and React&lt;/li&gt;
&lt;li&gt;Local Gatsby site with Markdown posts
&lt;ul&gt;
&lt;li&gt;If you have a Gatsby site &lt;em&gt;without&lt;/em&gt; Markdown posts check out the &lt;a href=&quot;#boilerplate-code&quot;&gt;Boilerplate Code&lt;/a&gt; or update the code in this tutorial to query posts from your data source instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;If you do not yet have Markdown files on your site then you should start by &lt;a href=&quot;https://www.gatsbyjs.org/docs/adding-markdown-pages/&quot;&gt;adding markdown pages&lt;/a&gt; to Gatsby. You can also learn more about &lt;a href=&quot;https://www.gatsbyjs.org/docs/adding-a-list-of-markdown-blog-posts/&quot;&gt; creating an index of markdown posts&lt;/a&gt; in the Gatsby Docs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Boilerplate Code: Query All Posts&lt;/h3&gt;
&lt;p&gt;If you do &lt;strong&gt;not&lt;/strong&gt; already have an index page listing all of your posts then create a new gatsby page for example named &quot;writing.js&quot; in &lt;code&gt;src&lt;/code&gt; within the &lt;code&gt;pages&lt;/code&gt; directory. This file will be responsible for rendering information about every post on your site.&lt;/p&gt;
&lt;p&gt;We will be using a GraphQL page query which allows the data returned from the query to be available to the component in the &lt;code&gt;data&lt;/code&gt; prop. The posts are returned by the page query and are equal to &lt;code&gt;data.allMarkdownRemark.edges&lt;/code&gt; . Once we have the posts we can &lt;code&gt;.map()&lt;/code&gt; through each of the posts and destructure the &lt;code&gt;node.frontmatter&lt;/code&gt; with &lt;code&gt;const { tags, title, date, description, slug } = node.frontmatter&lt;/code&gt;. This will add the title, date, description, and slug to the DOM for each post.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Gatsby uses the concept of a page query, which is a query for a specific page in a site. It is unique in that it can take query variables unlike Gatsby’s static queries.&quot; Source: &lt;a href=&quot;https://www.gatsbyjs.org/docs/page-query/&quot;&gt;Gatsby Docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Below is the boilerplate code that will be used throughout this tutorial:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import { Link, graphql } from &quot;gatsby&quot;;

const BlogIndex = (props) =&amp;gt; {
  const { data } = props;
  const posts = data.allMarkdownRemark.edges;

  return (
    &amp;lt;&amp;gt;
      {/* in my site I wrap each page with a Layout and SEO component which have
    been omitted here for clarity and replaced with a React.fragment --&amp;gt; */}

      {/*in-line css for demo purposes*/}
      &amp;lt;h1 style={{ textAlign: `center` }}&amp;gt;Writing&amp;lt;/h1&amp;gt;

      {posts.map(({ node }) =&amp;gt; {
        const { excerpt } = node;
        const { slug } = node.fields;

        const { title, date, description, slug } = node.frontmatter;
        return (
          &amp;lt;article key={slug}&amp;gt;
            &amp;lt;header&amp;gt;
              &amp;lt;h2&amp;gt;
                &amp;lt;Link to={slug}&amp;gt;{title}&amp;lt;/Link&amp;gt;
              &amp;lt;/h2&amp;gt;

              &amp;lt;p&amp;gt;{date}&amp;lt;/p&amp;gt;
            &amp;lt;/header&amp;gt;
            &amp;lt;section&amp;gt;
              &amp;lt;p
                dangerouslySetInnerHTML={{
                  __html: description || excerpt,
                }}
              /&amp;gt;
            &amp;lt;/section&amp;gt;
            &amp;lt;hr /&amp;gt;
          &amp;lt;/article&amp;gt;
        );
      })}
    &amp;lt;/&amp;gt;
  );
};

export default BlogIndex;

export const pageQuery = graphql`
  query {
    allMarkdownRemark(sort: { order: DESC, fields: frontmatter___date }) {
      edges {
        node {
          excerpt(pruneLength: 200)
          id
          frontmatter {
            title
            description
            date(formatString: &quot;MMMM DD, YYYY&quot;)
            tags
          }
          fields {
            slug
          }
        }
      }
    }
  }
`;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point you should be able to view an index of all of the posts on your site by running &lt;code&gt;gatsby develop&lt;/code&gt; and going to &lt;code&gt;http://localhost:8000/${NAME_OF_FILE}&lt;/code&gt;. For example, the file I created is named &lt;code&gt;writing.js&lt;/code&gt; so I navigate to &lt;a href=&quot;http://localhost:8000/writing&quot;&gt;http://localhost:8000/writing&lt;/a&gt; to view it. The page output by the boilerplate code above should resemble the below image (i.e., each blog post is listed along with its title, date, and description). Additionally, the header for each article should navigate to the slug for the article and be a valid link.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;lt;center&amp;gt;Index Page of All Posts&amp;lt;/center&amp;gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/list-of-posts.png&quot; alt=&quot;list of posts after setting up initial template&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Why Query All of The Posts?&lt;/h3&gt;
&lt;p&gt;Before filtering the posts its helpful fetch all of the posts before we return a filtered subset from all of the posts. On my site, I used a page query on the &lt;code&gt;/writing/&lt;/code&gt; page to retrieve data for all the blog posts from my site so that I can construct a list of posts. The results of the page query are available to this component within the &lt;code&gt;data&lt;/code&gt; prop to the component i.e., (&lt;code&gt;const { data } = props&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The boilerplate code above is a variation of the GraphQL query that my site uses to pull in each post along with its excerpt, id, frontmatter (title, category, description, date, slug, and tags). The blog posts are in the &lt;code&gt;allMarkdownRemark&lt;/code&gt; as &lt;code&gt;edges&lt;/code&gt; and can be accessed like &lt;code&gt;const posts = data.allMarkdownRemark.edges&lt;/code&gt;.You can use the above-provided query to return metadata and slugs for all posts OR if you already have a query to return an index of all blog posts then feel free to use that.&lt;/p&gt;
&lt;p&gt;Below is a photo that shows the data that the above GraphQL query returned for my site. You can view the data returned by that query for your particular site in an interactive format by running &lt;code&gt;gatsby develop&lt;/code&gt; and navigating to &lt;a href=&quot;http://localhost:8000/___graphql?query=%7BallMarkdownRemark(sort%3A%20%7B%20order%3A%20DESC%2C%20fields%3A%20frontmatter___date%20%7D)%20%7B%0A%20%20%20%20%20%20%20%20edges%20%7B%0A%20%20%20%20%20%20%20%20%20%20node%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20excerpt(pruneLength%3A%20200)%0A%20%20%20%20%20%20%20%20%20%20%20%20id%0A%20%20%20%20%20%20%20%20%20%20%20%20frontmatter%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20category%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20description%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20date(formatString%3A%20%22MMMM%20DD%2C%20YYYY%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20slug%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20tags%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20timeToRead%0A%20%20%20%20%20%20%20%20%20%20%20%20fields%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20slug%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%7D%0A&amp;amp;operationName=undefined&amp;amp;explorerIsOpen=false&quot;&gt;http://localhost:8000/___graphql&lt;/a&gt; and pressing run. If you go to &lt;a href=&quot;http://localhost:8000/___graphql?query=%7BallMarkdownRemark(sort%3A%20%7B%20order%3A%20DESC%2C%20fields%3A%20frontmatter___date%20%7D)%20%7B%0A%20%20%20%20%20%20%20%20edges%20%7B%0A%20%20%20%20%20%20%20%20%20%20node%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20excerpt(pruneLength%3A%20200)%0A%20%20%20%20%20%20%20%20%20%20%20%20id%0A%20%20%20%20%20%20%20%20%20%20%20%20frontmatter%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20title%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20category%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20description%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20date(formatString%3A%20%22MMMM%20DD%2C%20YYYY%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20slug%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20tags%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20timeToRead%0A%20%20%20%20%20%20%20%20%20%20%20%20fields%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20slug%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%7D%0A&amp;amp;operationName=undefined&amp;amp;explorerIsOpen=false&quot;&gt;http://localhost:8000/___graphql&lt;/a&gt; and scroll down you should see that there is metadata being returned for every single post on your site which is exactly what we are trying to capture before we filter posts.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;lt;center&amp;gt;Sample Data in GraphiQL&amp;lt;/center&amp;gt;&lt;/em&gt;
&lt;img src=&quot;/media/graphql-blog-query.png#center&quot; alt=&quot;output data&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;How to Filter Posts by User Input&lt;/h2&gt;
&lt;h3&gt;Capture User Input with Input Event&lt;/h3&gt;
&lt;p&gt;Now that we have the boilerplate code setup let&apos;s get back to the task at hand which is to filter the posts based on user input. &lt;em&gt;How can we capture what query a user is searching for and update the DOM with the appropriate post(s) accordingly?&lt;/em&gt; Well, there are various types of browser events including, &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;keypress&lt;/code&gt;, &lt;code&gt;click&lt;/code&gt;, &lt;code&gt;drag&lt;/code&gt; and &lt;code&gt;drop&lt;/code&gt;. When these events occur JavaScript can be written to respond based on the type and value of the event.&lt;/p&gt;
&lt;p&gt;Since we are having users type a search query into a &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; we can process their query as they type. We will be focusing on the &lt;code&gt;input&lt;/code&gt;event which triggers whenever the value in an input field changes. The &lt;code&gt;input&lt;/code&gt; event changes with each keystroke which is in contrast to the &lt;code&gt;change&lt;/code&gt; event which is fired once for each submission (i.e., pressing enter) for &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;,&lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; elements. You can read more about how &lt;a href=&quot;https://reactjs.org/docs/handling-events.html&quot;&gt;React handles events in the React docs&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Create Input Element with &lt;code&gt;onChange&lt;/code&gt; event handler&lt;/h3&gt;
&lt;p&gt;We already have the post data we need to filter available in the &lt;code&gt;data&lt;/code&gt; prop so let&apos;s create an element to allow users to type in their search query. &lt;code&gt;&amp;lt;input/&amp;gt;&lt;/code&gt; will have an &lt;code&gt;onChange&lt;/code&gt; property that calls a function &lt;code&gt;handleInputChange&lt;/code&gt; whenever the &lt;code&gt;&amp;lt;input/&amp;gt;&lt;/code&gt; changes and an &lt;code&gt;Input&lt;/code&gt; event is fired. In other words, &lt;code&gt;onChange&lt;/code&gt; calls another function which handles the Input event which fires every time someone types in our &lt;code&gt;&amp;lt;Input/&amp;gt;&lt;/code&gt;. So if someone typed &quot;React&quot; into an &lt;code&gt;&amp;lt;input/&amp;gt;&lt;/code&gt;. It will trigger 5 events with the following values (&quot;R&quot;, &quot;Re&quot;, &quot;Rea&quot;, &quot;Reac&quot;, &quot;React&quot;).&lt;/p&gt;
&lt;p&gt;Note: The &lt;code&gt;&amp;lt;input/&amp;gt;&lt;/code&gt; should go below the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; and outside of the &lt;code&gt;posts.map&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
        &amp;lt;h1 style={{ textAlign: `center` }}&amp;gt;Writing&amp;lt;/h1&amp;gt;
          &amp;lt;input
              type=&quot;text&quot;
              aria-label=&quot;Search&quot;
              placeholder=&quot;Type to filter posts...&quot;
              onChange={handleInputChange}
          /&amp;gt;
        {posts.map(({ node }) =&amp;gt; {
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The page should now visibly have an &lt;code&gt;&amp;lt;input/&amp;gt;&lt;/code&gt; element. However, it will not yet be functional as &lt;code&gt;handleInputChange&lt;/code&gt; has not been added yet.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;lt;center&amp;gt;Visible Input Element&amp;lt;/center&amp;gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/input-element.png&quot; alt=&quot;visible input element on DOM&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;useState() to Store Filtered Data and Query Information in State&lt;/h3&gt;
&lt;p&gt;Before implementing &lt;code&gt;onChange&lt;/code&gt; let&apos;s set the default state with &lt;code&gt;useState()&lt;/code&gt; for our search input with the default &lt;code&gt;query&lt;/code&gt; as an empty string and &lt;code&gt;filteredData&lt;/code&gt; as an empty array. You can &lt;a href=&quot;https://reactjs.org/docs/hooks-state.html&quot;&gt;read more about the &lt;code&gt;useState()&lt;/code&gt; hook in the React docs&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  const posts = data.allMarkdownRemark.edges
  const emptyQuery = &quot;&quot;
  const [state, setState] = useState({
    filteredData: [],
    query: emptyQuery,
  })
  return (
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Implement &lt;code&gt;onChange&lt;/code&gt; to Filter Posts by &lt;code&gt;&amp;lt;input/&amp;gt;&lt;/code&gt; Event Value&lt;/h3&gt;
&lt;p&gt;This &lt;code&gt;handleInputChange&lt;/code&gt; function takes the Input event in which the &lt;code&gt;event.target.value&lt;/code&gt; is the query string that is being searched for. &lt;code&gt;handleInputChange&lt;/code&gt; also has access to our props which contain all of the posts for the site. So we can filter all of the site&apos;s posts based on the &lt;code&gt;query&lt;/code&gt; and return &lt;code&gt;filteredPosts&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In order to process the event (which fires on each keystroke) we need to implement &lt;code&gt;handleInputChange&lt;/code&gt;. &lt;code&gt;handleInputChange&lt;/code&gt; receives an Input event. The &lt;code&gt;target.value&lt;/code&gt; from the &lt;code&gt;event&lt;/code&gt; is the string that the user typed and we will store that in the &lt;code&gt;query&lt;/code&gt; variable.&lt;/p&gt;
&lt;p&gt;Inside of &lt;code&gt;handleInputChange&lt;/code&gt; we have access to the posts and the query so let&apos;s update the code to &lt;code&gt;.filter()&lt;/code&gt; the posts based on the query. First, we should standardize the casing of the fields and the query with &lt;code&gt;.toLowerCase()&lt;/code&gt; so that if someone types &quot;JaVAsCriPt&quot; it should return posts that match &quot;JavaScript&quot;. For our &lt;code&gt;.filter()&lt;/code&gt; if any of the three conditions that check if the post contains the &lt;code&gt;query&lt;/code&gt; evaluates to true then that post will be returned in the &lt;code&gt;filteredData&lt;/code&gt; array.&lt;/p&gt;
&lt;p&gt;After we filter the data in &lt;code&gt;handleInputChange&lt;/code&gt; the state should be updated with the current &lt;code&gt;query&lt;/code&gt; and the &lt;code&gt;filteredData&lt;/code&gt; that resulted from that query.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
  const [state, setState] = useState({
    filteredData: [],
    query: emptyQuery,
  })

const handleInputChange = event =&amp;gt; {
  const query = event.target.value
  const { data } = props

  // this is how we get all of our posts
  const posts = data.allMarkdownRemark.edges || []


   // return all filtered posts
  const filteredData = posts.filter(post =&amp;gt; {
    // destructure data from post frontmatter
    const { description, title, tags } = post.node.frontmatter
    return (
      // standardize data with .toLowerCase()
      // return true if the description, title or tags
      // contains the query string
      description.toLowerCase().includes(query.toLowerCase()) ||
      title.toLowerCase().includes(query.toLowerCase()) ||
      (tags &amp;amp;&amp;amp; tags
        .join(&quot;&quot;) // convert tags from an array to string
        .toLowerCase()
        .includes(query.toLowerCase()))
    )
  })

  // update state according to the latest query and results
  setState({
    query, // with current query string from the `Input` event
    filteredData, // with filtered data from posts.filter(post =&amp;gt; (//filteredData)) above
  })
}

return (
    &amp;lt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if you type in the &lt;code&gt;&amp;lt;Input/&amp;gt;&lt;/code&gt; now it still won&apos;t update the list of posts because we are always rendering the same posts regardless of if we have &lt;code&gt;filteredData&lt;/code&gt; available in the state or not. But if you were to &lt;code&gt;console.log(event.target.value)&lt;/code&gt; in &lt;code&gt;handleInputChange&lt;/code&gt; we can confirm that &lt;code&gt;handleInput&lt;/code&gt; is firing properly by typing &quot;React&quot;. Even though the page doesn&apos;t visually change the console output should be something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;r writing.js:1
re writing..js:1
rea writing..js:1
reac writing.js:1
react writing.js:1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Display Filtered Posts&lt;/h3&gt;
&lt;p&gt;We are already storing &lt;code&gt;filteredData&lt;/code&gt; and &lt;code&gt;query&lt;/code&gt; in state but let&apos;s rename &lt;code&gt;posts&lt;/code&gt; to &lt;code&gt;allPosts&lt;/code&gt; so that we can make the value of &lt;code&gt;posts&lt;/code&gt; conditional based on whether or not a user has typed a search query and should see their filtered search query results as &lt;code&gt;posts&lt;/code&gt; or if they have yet to type a query then we should display all of the blog posts.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const BlogIndex = props =&amp;gt; {
const { filteredData, query } = state
const { data } = props
 // let&apos;s rename posts to all posts
const allPosts = data.allMarkdownRemark.edgess
const emptyQuery = &quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the &lt;code&gt;posts&lt;/code&gt; we need to decide whether to return all of the posts or the filtered posts by checking &lt;code&gt;state&lt;/code&gt; and conditionally rendering either all of the posts OR just the filtered posts based on whether or not we have &lt;code&gt;filteredData&lt;/code&gt; and the &lt;code&gt;query != emptyQuery&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The below code updates our render logic accordingly.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const { filteredData, query } = state;
// if we have a fileredData in state and a non-emptyQuery then
// searchQuery then `hasSearchResults` is true
const hasSearchResults = filteredData &amp;amp;&amp;amp; query !== emptyQuery;

// if we have a search query then return filtered data instead of all posts; else return allPosts
const posts = hasSearchResults ? filteredData : allPosts;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;You should now have a working post filter on your blog index page (if not check out the &lt;a href=&quot;#final-code&quot;&gt;Final Code&lt;/a&gt; below). At a high-level the steps taken to implement filtering were:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;create a page query to implement a blog index page which lists all of the posts&lt;/li&gt;
&lt;li&gt;create an input field on the blog index page with an onChange event handler to process keystrokes in our input field&lt;/li&gt;
&lt;li&gt;filter all of the posts on the blog index page based on the current query (from input event) and use &lt;code&gt;useState()&lt;/code&gt; to update the state with the search query and filtered data&lt;/li&gt;
&lt;li&gt;update rendering logic to either display all of the posts or the filtered posts on the blog index page based on whether or not there&apos;s a query in state&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Below is the final code as outlined in the tutorial. However, this is just the baseline for search and you may want to make the functionality more robust by adding additional features such as autocomplete suggestions, displaying the number of results (based on length of &lt;code&gt;posts&lt;/code&gt;) and providing an empty state with messaging for when there are zero results (based on filteredData being an empty array).&lt;/p&gt;
&lt;h3&gt;Final Code&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import React, { useReact } from &quot;react&quot;;
import { Link, graphql } from &quot;gatsby&quot;;

const BlogIndex = (props) =&amp;gt; {
  const { data } = props;
  const allPosts = data.allMarkdownRemark.edges;

  const emptyQuery = &quot;&quot;;

  const [state, setState] = useState({
    filteredData: [],
    query: emptyQuery,
  });

  const handleInputChange = (event) =&amp;gt; {
    console.log(event.target.value);
    const query = event.target.value;
    const { data } = props;

    const posts = data.allMarkdownRemark.edges || [];

    const filteredData = posts.filter((post) =&amp;gt; {
      const { description, title, tags } = post.node.frontmatter;
      return (
        description.toLowerCase().includes(query.toLowerCase()) ||
        title.toLowerCase().includes(query.toLowerCase()) ||
        (tags &amp;amp;&amp;amp; tags.join(&quot;&quot;).toLowerCase().includes(query.toLowerCase()))
      );
    });

    setState({
      query,
      filteredData,
    });
  };

  const { filteredData, query } = state;
  const hasSearchResults = filteredData &amp;amp;&amp;amp; query !== emptyQuery;
  const posts = hasSearchResults ? filteredData : allPosts;

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1 style={{ textAlign: `center` }}&amp;gt;Writing&amp;lt;/h1&amp;gt;

      &amp;lt;div className=&quot;searchBox&quot;&amp;gt;
        &amp;lt;input
          className=&quot;searchInput&quot;
          type=&quot;text&quot;
          aria-label=&quot;Search&quot;
          placeholder=&quot;Type to filter posts...&quot;
          onChange={handleInputChange}
        /&amp;gt;
      &amp;lt;/div&amp;gt;

      {posts.map(({ node }) =&amp;gt; {
        const { excerpt } = node;

        const { slug } = node.fields;
        const { tags, title, date, description } = node.frontmatter;
        return (
          &amp;lt;article key={slug}&amp;gt;
            &amp;lt;header&amp;gt;
              &amp;lt;h2&amp;gt;
                &amp;lt;Link to={slug}&amp;gt;{title}&amp;lt;/Link&amp;gt;
              &amp;lt;/h2&amp;gt;

              &amp;lt;p&amp;gt;{date}&amp;lt;/p&amp;gt;
            &amp;lt;/header&amp;gt;
            &amp;lt;section&amp;gt;
              &amp;lt;p
                dangerouslySetInnerHTML={{
                  __html: description || excerpt,
                }}
              /&amp;gt;
            &amp;lt;/section&amp;gt;
            &amp;lt;hr /&amp;gt;
          &amp;lt;/article&amp;gt;
        );
      })}
    &amp;lt;/&amp;gt;
  );
};

export default BlogIndex;

export const pageQuery = graphql`
  query {
    allMarkdownRemark(sort: { order: DESC, fields: frontmatter___date }) {
      edges {
        node {
          excerpt(pruneLength: 200)
          id
          frontmatter {
            title
            description
            date(formatString: &quot;MMMM DD, YYYY&quot;)

            tags
          }

          fields {
            slug
          }
        }
      }
    }
  }
`;
&lt;/code&gt;&lt;/pre&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Creating New Supabase Users In NextJS</title><link>https://aboutmonica.com/blog/creating-new-supabase-users-in-next-js/</link><guid isPermaLink="true">https://aboutmonica.com/blog/creating-new-supabase-users-in-next-js/</guid><description>This article walks through how to create new users for a Supabase database with an API written in NextJS</description><pubDate>Tue, 09 Mar 2021 12:01:39 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;garden&quot;&amp;gt;
This article is the beginning of a series about setting up a NextJS with
Supabase for user management and database storage. View the next part of this
series:{&quot; &quot;}
&amp;lt;a href=&quot;/blog/creating-protected-routes-in-next-js-with-supabase&quot;&amp;gt;
creating protected routes with NextJS and Supabase
&amp;lt;/a&amp;gt;
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;This article walks through how to create new users for a &lt;a href=&quot;https://supabase.io/&quot;&gt;Supabase&lt;/a&gt; database with an API written in NextJS. Note: at the time of this writing that Supabase is free for &amp;lt;a href=&quot;https://supabase.io/beta&quot;&amp;gt;beta&amp;lt;/a&amp;gt; users which is pretty nifty as they include a hosted Postgres database which makes it faster to get an application up and running with a functional database. After the Beta period wraps up Supabase is &amp;lt;a href=&quot;https://supabase.io/docs/pricing&quot;&amp;gt;planning on charging for hosting&amp;lt;/a&amp;gt; and will offer current Beta users 1 year of base tier usage for free.&lt;/p&gt;
&lt;p&gt;I am currently building a SaaS (Software as a Service) website along with some other &lt;a href=&quot;https://egghead.io/&quot;&gt;Egghead&lt;/a&gt; folks who are creating different types of SaaS applications. I am building this app from &quot;scratch&quot; and am currently in the phase of setting up authentication. For this project I am focused on learning new technology and documenting my learnings therefore I decided to try out Supabase, which is an Open Source alternative to Google&apos;s &lt;a href=&quot;https://firebase.google.com/&quot;&gt;Firebase&lt;/a&gt;. The specific application I am working towards building is, &amp;lt;a href=&quot;https://shinedocs.com&quot;&amp;gt;Shine Docs&amp;lt;/a&amp;gt; which will allow folks to document professional achievements in a granular way.&lt;/p&gt;
&lt;p&gt;Here&apos;s a blurb from the project&apos;s README:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[...]your place to shine, showcase and document more granular professional (or otherwise) achievements. Did you fix a gnarly production bug in record time? Did you give your 1st (or 20th) conference talk? Did you write out some solid documentation? Make sure you capture all of this awesomeness in more in a dedicated space. These types of receipts translate well into future resumes, negotiations, annual reviews and reminders to yourself. Inspired by Julia Evan’s article on &lt;a href=&quot;https://jvns.ca/blog/brag-documents/&quot;&gt;getting your work recognized with a brag document&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;This application is intended for folks looking for more structure with how they document granular professional achievements as well as those looking to have their achievements be more visible.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Set Up NextJS&lt;/h2&gt;
&lt;p&gt;If you do not yet have a NextJS site you should set up a boilerplate NextJS site as a starting point. This can be done by running the command &lt;code&gt;npx create-next-app&lt;/code&gt; to spinup up a default NextJS site. After going through the prompts you should open your newly created directory containing the site.&lt;/p&gt;
&lt;p&gt;The next step in setting up NextJS to interact with Supabase is to install Supabase dependencies with &lt;code&gt;@supabase/supabase-js&lt;/code&gt; and then run &lt;code&gt;yarn dev&lt;/code&gt; to run the site locally. If everything worked out you should be able to visit &lt;code&gt;localhost:3000&lt;/code&gt; and see your Next site running.&lt;/p&gt;
&lt;h2&gt;Set up Supabase Project&lt;/h2&gt;
&lt;p&gt;On &lt;a href=&quot;https://app.supabase.io/&quot;&gt;Supabase we will create a new project&lt;/a&gt; and then retrieve the API key and URL from &lt;code&gt;https://app.supabase.io/project/yourprojecturl]/settings/api&lt;/code&gt; which can be navigated to by going to your project &amp;gt; settings &amp;gt; API.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/supabase-settings.png&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;a screenshot of the Supabase settings page&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In order for our application to be able to interact with our project&apos;s DB we will use environment variables to store the necessary values. As a Mac user, I tend to store environment variables in &lt;code&gt;~/.bash_profile&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can add the following your &lt;code&gt;~/.bash_profile&lt;/code&gt; or wherever you store local environment variables:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export SUPABASE_KEY=&quot;SUPABASEKEYFROMSETTINGSSCREEN&quot;
export SUPABASE_URL=&quot;SUPABASEURLFROMSETTINGSSCREEN&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you already have a terminal session running then after you save your environment variables you should run &lt;code&gt;source ~/.bash_profile&lt;/code&gt; to ensure that the newly exported environment variables are available to your NextJS app to access.&lt;/p&gt;
&lt;p&gt;We will then create a &lt;code&gt;supabaseClient.js&lt;/code&gt; file (in &lt;code&gt;utils/&lt;/code&gt;) to set up the Supabase client that is used to interact with Supabase DB to use the URL and API key that were set in the previous step.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createClient } from &quot;@supabase/supabase-js&quot;;

// retrieving environment variables
const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_KEY;

export const supabase = createClient(supabaseUrl, supabaseKey);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Having the Supabase client live in a standalone file will be helpful when we have multiple API endpoints that interact with Supabase that require the same credentials.&lt;/p&gt;
&lt;h2&gt;Registering Supabase User&lt;/h2&gt;
&lt;p&gt;Now we will call Supabase to register users by creating a new API function inside of &lt;code&gt;pages/api&lt;/code&gt; that uses our Supabase client.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { supabase } from &quot;../../utils/supabaseClient&quot;;

export default async function registerUser(req, res) {
  // destructure the e-mail and password received in the request body.
  const { email, password } = req.body;

  //make a SignUp attempt to Supabase and
  // capture the user (on success) and/or error.

  let { user, error } = await supabase.auth.signUp({
    email: email,
    password: password,
  });
  // Send a 400 response if something went wrong
  if (error) return res.status(401).json({ error: error.message });
  // Send 200 success if there were no errors!
  // and also return a copy of the object we received from Supabase
  return res.status(200).json({ user: user });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can learn more about HTTP status codes on what different status codes mean on my site https://www.httriri.com/.&lt;/p&gt;
&lt;p&gt;Now, let&apos;s actually use the Supabase endpoint to create users. Our site&apos;s visitors will fill out a form to register therefore let&apos;s create a form that requires an e-mail and password and calls the previously created Register endpoint upon form submission. This form will then be imported and used in our index file, &lt;code&gt;index.js&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;form onSubmit={registerUser}&amp;gt;
  &amp;lt;label htmlFor=&quot;email&quot;&amp;gt;Email&amp;lt;/label&amp;gt;
  &amp;lt;input id=&quot;email&quot; name=&quot;email&quot; type=&quot;email&quot; autoComplete=&quot;email&quot; required /&amp;gt;
  &amp;lt;label htmlFor=&quot;password&quot;&amp;gt;Password&amp;lt;/label&amp;gt;

  &amp;lt;input type=&quot;password&quot; id=&quot;password&quot; name=&quot;password&quot; required /&amp;gt;
  &amp;lt;button type=&quot;submit&quot;&amp;gt;Register&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let&apos;s define what happens &lt;code&gt;onSubmit&lt;/code&gt; when &lt;code&gt;registerUser&lt;/code&gt; is called by defining &lt;code&gt;registerUser&lt;/code&gt;. This function will receive the email and password typed into the form from the form submission event and will make a post request to the register endpoint.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default function Form() {
  const registerUser = async (event) =&amp;gt; {
    event.preventDefault(); // prevents page from redirecting on form submissiomn

    // call default function in pages/api/register
    // send the email and password from form submission event to that endpoint
    const res = await fetch(&quot;/api/register&quot;, {
      body: JSON.stringify({
        email: event.target.email.value,
        password: event.target.password.value,
      }),
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;,
      },
      method: &quot;POST&quot;,
    });

    const result = await res.json();
  };

  return &amp;lt;form onSubmit={registerUser}&amp;gt;// above form omitted for brevity&amp;lt;/form&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let&apos;s look at the response that we are getting back from the API request to the register endpoint.If we destructure &lt;code&gt;res.json()&lt;/code&gt; like &lt;code&gt;const { user } = await res.json()&lt;/code&gt; then we can see the &lt;code&gt;user&lt;/code&gt; object for a successful request looks something like&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;user {
  id: &apos;2de33395-b88b-4004&apos;,
  aud: &apos;authenticated&apos;,
  role: &apos;authenticated&apos;,
  email: &apos;test@example.com&apos;,
  confirmation_sent_at: &apos;2021-03-09T12:35:02.895833829Z&apos;,
  app_metadata: { provider: &apos;email&apos; },
  user_metadata: {},
  created_at: &apos;2021-03-09T12:08:46.611304Z&apos;,
  updated_at: &apos;2021-03-09T12:35:03.466491Z&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we&apos;ve received a 200 response (no errors!) and a user object back from our SignUp call to Supabase then we can redirect users to a message prompting them to confirm their e-mail address. We can use the NextJS router to handle this redirect:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useRouter } from &quot;next/router&quot;;

export default function Form() {
  const router = useRouter();
  const registerUser = async (event) =&amp;gt; {
    event.preventDefault();

    const res = await fetch(&quot;/api/register&quot;, {
      body: JSON.stringify({
        email: event.target.email.value,
        password: event.target.password.value,
      }),
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;,
      },
      method: &quot;POST&quot;,
    });

    const { user } = await res.json();
    if (user) router.push(`/welcome?email${user.email}`);
  };

  return &amp;lt;form onSubmit={registerUser}&amp;gt;{/*omitted for brevity*/}&amp;lt;/form&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we are currently redirecting folks to a Welcome page that doesn&apos;t exist so let&apos;s create a new page &lt;code&gt;page/welcome.js&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Footer from &quot;../components/footer&quot;;
import { useRouter } from &quot;next/router&quot;;

export default function Welcome() {
  const router = useRouter();
  const { email } = router.query;
return (
      &amp;lt;main&amp;gt;
        &amp;lt;p&amp;gt;
          Thank you for signing up. Please check your {email} inbox to verify
          your e-mail address!
        &amp;lt;/p&amp;gt;
      &amp;lt;/main&amp;gt;
      &amp;lt;Footer /&amp;gt;

    &amp;lt;/div&amp;gt;

);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If all went well then if you fill out the form with an email address and password then you should be redirected to the welcome screen, receive a confirmation e-mail from Supabase to the e-mail you submitted and see under the Authentication section of your project at &lt;code&gt;https://app.supabase.io/project/[yourprojecturl]/auth/users&lt;/code&gt; that there is a new user in your users table with the email address that you just submitted. By default Supabase is set up to not allow users to log in until they verify their e-mail address, so unless you&apos;ve changed that setting you should see the column &lt;code&gt;Last Sign In&lt;/code&gt; showing the value &quot;Waiting for verification...&quot;.&lt;/p&gt;
&lt;h2&gt;Example Code on GitHub&lt;/h2&gt;
&lt;p&gt;Checkout out the example code for this article at: https://github.com/M0nica/register-supabase-users-nextjs-example&lt;/p&gt;
&lt;p&gt;That&apos;s all I have for now! But I am looking forward to sharing more about how I implement Supabase as I continue my app development.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Creating Protected Routes In NextJS With Supabase</title><link>https://aboutmonica.com/blog/creating-protected-routes-in-next-js-with-supabase/</link><guid isPermaLink="true">https://aboutmonica.com/blog/creating-protected-routes-in-next-js-with-supabase/</guid><description>This articles walks through how to create protected routes on NextJS with Supabase&amp;#x27;s user management.</description><pubDate>Sat, 13 Mar 2021 23:27:48 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;garden&quot;&amp;gt;
This articles walks through how to create protected routes on NextJS with
Supabase&apos;s user management. It assumes you already have a NextJS site up
and running with the ability to create new Supabase users but if not check out
the first part of this series on{&quot; &quot;}
&amp;lt;a href=&quot;/blog/creating-new-supabase-users-in-next-js&quot;&amp;gt;
creating new Supabase users in NextJS
&amp;lt;/a&amp;gt;
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h2&gt;Supabase Auth Overview&lt;/h2&gt;
&lt;p&gt;Supabase has various methods in their JavaScript client library to handle user authentication and uses JSON Web Tokens (JWT) under the hood to manage authentication. If you want to learn more about how Auth works in Supabase check out the &lt;a href=&quot;https://supabase.io/docs/learn/auth-deep-dive/auth-deep-dive-jwts&quot;&gt;Supabase auth deep-dive video series&lt;/a&gt;. In order to have protected routes on our NextJS site, we&apos;ll need a way to register and authenticate users. We can perform these user actions and checks with the following methods from the &lt;a href=&quot;https://supabase.io/docs/client/supabase-client&quot;&gt;Supabase Auth client&lt;/a&gt;. :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://supabase.io/docs/client/auth-signup&quot;&gt;supabase.auth.signUp&lt;/a&gt; - We should give users the ability to create an account (covered in the first article on &lt;a href=&quot;/blog/creating-new-supabase-users-in-next-js&quot;&gt;creating new Supabase users in NextJS&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://supabase.io/docs/client/auth-signin&quot;&gt;supabase.auth.signIn&lt;/a&gt; - We need to give users the ability to sign-in. In this particular article, we&apos;ll cover the traditional method of using a username and password for sign-in but Supabase also supports other ways to log in, including OAuth providers (GitHub, Google, etc.) and magic links.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://supabase.io/docs/client/auth-user&quot;&gt;supabase.auth.user&lt;/a&gt; - We need a way to determine if a user is currently logged-in in order to ensure that logged-out users are not able to view pages that should only be accessible to logged-in users and that the proper information is displayed in various places like the site navigation.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://supabase.io/docs/client/auth-signout&quot;&gt;supabase.auth.signOut&lt;/a&gt; - We should give users the ability to sign out and unauthenticate their session.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Create Protected Route&lt;/h2&gt;
&lt;p&gt;In order to create a protected route we need to have a particular page component we&apos;d like to protect. For this example let&apos;s created a protected page at &lt;code&gt;pages/protected.js&lt;/code&gt; that we can view at &lt;code&gt;localhost:3000/protected&lt;/code&gt; when our site is running locally. This protected page will make a fetch request to a &lt;code&gt;getUser&lt;/code&gt; API route to determine if there is currently an authenticated user loading the page. The API call should return the current user when there is one. We can then use this API response to redirect the page to the login page when there is no current user and only display user-specific information on the protected route when there is a user.&lt;/p&gt;
&lt;p&gt;The API request can be made with &lt;code&gt;getServerSideProps()&lt;/code&gt; which is a NextJS function that is called before a page renders. This allows us to redirect before the page renders based on the response from the &lt;code&gt;getUser&lt;/code&gt; API call.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { basePath } from &quot;../utils/siteConfig&quot;;

export async function getServerSideProps() {
    // We need to implement `/api/getUser` by creating
    // an endpoint in `pages/api` but for now let&apos;s just call it
  const response = await fetch(`${basePath}/api/getUser`).then((response) =&amp;gt;
    response.json()
  );

  const { user } = response;

 // If the `getUser` endpoint doesn&apos;t have a user in its response
 // then we will redirect to the login page
 // which means this page will only be viewable when `getUser` returns a user.

  if (!user) {
    return {
      redirect: { destination: &quot;/login&quot;, permanent: false },
    };
  }
  // We&apos;ll pass the returned `user` to the page&apos;s React Component as a prop
  return { props: { user } };
}
export default function Protected({ user }) {
  return (
          &amp;lt;p&amp;gt;
          // Let&apos;s greet the user by their e-mail address
            Welcome {user.email}!{&quot; &quot;}
            &amp;lt;span role=&quot;img&quot; aria-label=&quot;waving hand&quot;&amp;gt;
              👋🏾
            &amp;lt;/span&amp;gt;{&quot; &quot;}
          &amp;lt;/p&amp;gt;{&quot; &quot;}
          You are currently viewing a top secret page!
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;danger&quot;&amp;gt;
In this instance, NextJS requires absolute paths for the API routes and if you do not have an absolute route then you&apos;ll receive the following error:
&amp;lt;b&amp;gt;&quot;Error: only absolute urls are supported&quot;&amp;lt;/b&amp;gt;. In order to resolve this I created a helper function in &amp;lt;b&amp;gt;utils/siteConfig&amp;lt;/b&amp;gt; to set the basePath based on the environment. In order for this to work there needs to be a &amp;lt;b&amp;gt;PRODUCTION_URL&amp;lt;/b&amp;gt; set in your deployed site&apos;s environment variables.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const dev = process.env.NODE_ENV !== &quot;production&quot;;
export const basePath = dev
  ? &quot;http://localhost:3000&quot;
  : process.env.PRODUCTION_URL;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;Now, we need to actually implement the &lt;code&gt;getUser&lt;/code&gt; API route that the protected route is calling by creating a file &lt;code&gt;pages/api/getUser.js&lt;/code&gt;. Within this file we will make a request to &lt;code&gt;supabase.auth.user()&lt;/code&gt; which returns the current user when there is a user currently logged-in.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { supabase } from &quot;../../utils/supabaseClient&quot;;

export default async function getUser(req, res) {
  const user = await supabase.auth.user();
  return res.status(200).json({ user: user });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above code assumes that you&apos;ve already set up a Supabase Client which we covered in the &amp;lt;a href=&quot;/blog/creating-new-supabase-users-in-next-js&quot;&amp;gt;first post of this series&amp;lt;/a&amp;gt;. The Supabase client we are using in this instance looks like the below and uses environment variables to determine the Supabase DB URL and associated key:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createClient } from &quot;@supabase/supabase-js&quot;;

const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_KEY;

export const supabase = createClient(supabaseUrl, supabaseKey);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can retrieve the API key and database URL associated with your Supabase project from &lt;code&gt;https://app.supabase.io/project/yourprojecturl]/settings/api&lt;/code&gt; which can be navigated to by going to your project &amp;gt; settings &amp;gt; API.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/supabase-settings.png&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;a screenshot of the Supabase settings page&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Sign-in and Redirect to Protected Page&lt;/h2&gt;
&lt;p&gt;We&apos;ll allow folks to log in and log out of the site using the sitewide navigation. In order to show the appropriate links based on authentication status, we can use the state to track if a user is currently authenticated. As a default, we&apos;ll set authentication status to &lt;code&gt;false&lt;/code&gt; so that the navigation defaults to thelogged-out view.&lt;/p&gt;
&lt;p&gt;When a user is authenticated then we will show the Sign Out text in the nav: &lt;img src=&quot;/media/logged-in-nav.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If there is no authenticated user then we will link to the Sign-in and Sign Up pages: &lt;img src=&quot;/media/logged-out-nav.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Link from &quot;next/link&quot;;
import { useEffect, useState } from &quot;react&quot;;

export default function Header() {
  const router = useRouter();
  // Let&apos;s use state to track if a user is currently authenticated
  // As a default we&apos;ll set this value to false so that the navigation defaults to thelogged-out view
  const [isAuthed, setAuthStatus] = useState(false);

  // We&apos;ll set up the nav, on mount to call the getUser endpoint we just
  // created to determine if a user is currently logged-in or not
  useEffect(() =&amp;gt; {
    fetch(&quot;./api/getUser&quot;)
      .then((response) =&amp;gt; response.json())
      .then((result) =&amp;gt; {
        setAuthStatus(result.user &amp;amp;&amp;amp; result.user.role === &quot;authenticated&quot;);
      });
  }, []);

  return (
    &amp;lt;nav&amp;gt;
      &amp;lt;div&amp;gt;
        // If user is authenticated then we will show the Sign Out text
        {isAuthed ? (
          &amp;lt;span&amp;gt;
            &amp;lt;h3&amp;gt;Sign Out &amp;amp;rarr;&amp;lt;/h3&amp;gt;
          &amp;lt;/span&amp;gt;
        ) : (
          // If there is no authenticated user then we will link to the Sign-in and Sign Up pages
          &amp;lt;&amp;gt;
            &amp;lt;Link href=&quot;/signup&quot;&amp;gt;
              &amp;lt;h3&amp;gt;Sign Up &amp;amp;rarr;&amp;lt;/h3&amp;gt;
            &amp;lt;/Link&amp;gt;
            &amp;lt;Link href=&quot;/login&quot;&amp;gt;
              &amp;lt;h3&amp;gt;Login &amp;amp;rarr;&amp;lt;/h3&amp;gt;
            &amp;lt;/Link&amp;gt;
          &amp;lt;/&amp;gt;
        )}
      &amp;lt;/div&amp;gt;
    &amp;lt;/nav&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When a user clicks &quot;Sign In&quot; from the nav we will navigate the user to the &lt;code&gt;login&lt;/code&gt; page which contains a form to allow users to sign-in. The form will collect a user&apos;s email and password and on submit will fire a function &lt;code&gt;signInUser&lt;/code&gt; which makes an API request to an API route for &lt;code&gt;login&lt;/code&gt; and passes the &lt;code&gt;email&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; values from the form submit event to the API. If all goes well then we will receive a user object and can redirect (using NextJS&apos;s client-side router) to the &lt;code&gt;/protected&lt;/code&gt; route that serves as a landing page for logged-in users.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useRouter } from &quot;next/router&quot;;

export default function Form() {
  const router = useRouter();
  const signInUser = async (event) =&amp;gt; {
    event.preventDefault();

    const res = await fetch(`/api/login`, {
      body: JSON.stringify({
        email: event.target.email.value,
        password: event.target.password.value,
      }),
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;,
      },
      method: &quot;POST&quot;,
    });

    const { user } = await res.json();
    if (user) router.push(`/protected`);
  };

  return (
    &amp;lt;form onSubmit={signInUser}&amp;gt;
      &amp;lt;label htmlFor=&quot;email&quot;&amp;gt;Email&amp;lt;/label&amp;gt;
      &amp;lt;input
        id=&quot;email&quot;
        name=&quot;email&quot;
        type=&quot;email&quot;
        autoComplete=&quot;email&quot;
        required
      /&amp;gt;
      &amp;lt;label htmlFor=&quot;password&quot;&amp;gt;Password&amp;lt;/label&amp;gt;

      &amp;lt;input type=&quot;password&quot; id=&quot;password&quot; name=&quot;password&quot; required /&amp;gt;
      &amp;lt;button type=&quot;submit&quot;&amp;gt;Login&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;login&lt;/code&gt; API route will use &lt;code&gt;supabase.auth.signIn&lt;/code&gt; to sign-in a user. If a user is successfully signed in then the API will return a 200 response, or else the API will return a 401 response. The form is not yet set up to handle this 401 response but ideally, we&apos;d want to return some type of message to the user informing them that their credentials were invalid and prompt them to attempt to sign-in again or reset their password. However, as this app is currently being built the functionality to reset password does not yet exist so this error path cannot be fully handled yet.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { supabase } from &quot;../../utils/supabaseClient&quot;;

export default async function registerUser(req, res) {
  const { email, password } = req.body;
  let { user, error } = await supabase.auth.signIn({
    email: email,
    password: password,
  });
  if (error) return res.status(401).json({ error: error.message });
  return res.status(200).json({ user: user });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Sign Out and Redirect to Homepage&lt;/h2&gt;
&lt;p&gt;Let&apos;s update the Sign Out link in the header to be functional by creating a &lt;code&gt;signOut&lt;/code&gt; function that fires on click of the Sign Out text.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;span onClick={signOutUser}&amp;gt;
  &amp;lt;h3&amp;gt;Sign Out &amp;amp;rarr;&amp;lt;/h3&amp;gt;
&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&apos;ll also want to import a router from &lt;code&gt;next/router&lt;/code&gt; to handle our client-side redirect.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useRouter } from &quot;next/router&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For &lt;code&gt;signOutUser&lt;/code&gt; let&apos;s make a call to a &lt;code&gt;logout&lt;/code&gt; API route that sets the &lt;code&gt;authStatus&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; when a user is successfully signed out. We also want to ensure that when a user is not logged-in they are not viewing an authenticated page by redirecting to the homepage if a user logs out on a page other than the homepage. Without explicitly redirecting to the homepage when a user signs out, the state of &lt;code&gt;authStatus&lt;/code&gt; would change in the nav as well as the logged-in vs.logged-out specific text however, the actual page regardless of authentication would continue showing protected information for unauthenticated users which we don&apos;t want.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const signOutUser = async () =&amp;gt; {
  const res = await fetch(`/api/logout`);
  if (res.status === 200) setAuthStatus(false);
  // redirect to homepage when logging out users
  if (window.location !== &quot;/&quot;) router.push(&quot;/&quot;);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we need to create the &lt;code&gt;/api/logout&lt;/code&gt; route so that we can actually use it when the &lt;code&gt;signOutUser&lt;/code&gt; function fires.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { supabase } from &quot;../../utils/supabaseClient&quot;;

export default async function logoutUser(req, res) {
  let { error } = await supabase.auth.signOut();

  if (error) return res.status(401).json({ error: error.message });
  return res.status(200).json({ body: &quot;User has been logged out&quot; });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;So in conclusion, we created a protected route by creating a page component in NextJS that calls a &lt;code&gt;getUser&lt;/code&gt; endpoint in &lt;code&gt;getServerSideProps()&lt;/code&gt; and redirects to the log in page, instead of loading the protected route, when there is not a user returned. We also set up client-side routing to redirect users to &lt;code&gt;/protected&lt;/code&gt; when they successfully logged-in and to the homepage &lt;code&gt;/&lt;/code&gt; when they logged out. The core functionality to update and check authentication was handle in API routes using Supabase&apos;s various auth methods (signIn, signOut, user).&lt;/p&gt;
&lt;h2&gt;Example Code on GitHub&lt;/h2&gt;
&lt;p&gt;You can view the full source code for the example code at: https://github.com/M0nica/protected-routes-with-supabase-nextjs-example&lt;/p&gt;
&lt;h2&gt;Looking Ahead&lt;/h2&gt;
&lt;p&gt;I am looking forward to sharing more about the app development as I progress through my journey of developing &amp;lt;a href=&quot;https://shinedocs.com&quot;&amp;gt;Shine Docs&amp;lt;/a&amp;gt; . As I wrap up the authentication for this site I am considering adding additional functionality like magic links or other auth providers, which are natively supported by Supabase. Before I extend the auth functionality to support additional ways of authenticating I will need to update the site to give users the ability to reset their own password and better handle authentication errors to ensure that the sign-in (are the user credentials invalid? did something go wrong during sign-in?) and sign up (has an e-mail already been claimed? is a password not secure enough?) flow are as seamless as possible.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Custom ZSH Startup Message</title><link>https://aboutmonica.com/blog/customizing-zsh-startup/</link><guid isPermaLink="true">https://aboutmonica.com/blog/customizing-zsh-startup/</guid><description>This post discusses how to customize the ZSH startup message that appears when opening a new terminal window.</description><pubDate>Wed, 28 Aug 2024 13:12:21 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;This post discusses how to customize the ZSH startup message displayed when opening a new terminal window. I recently synced my &lt;a href=&quot;https://iterm2.com/&quot;&gt;iTerm2&lt;/a&gt; settings across my computer. I decided while tidying up my local development to update the ZSH startup message that appears on load to include a message and ASCII art.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[ASCII art] is the most universal computer art form in the world -- every computer system capable of displaying multi-line text can display ASCII art, without needing to have a graphics mode or support a particular graphics file format.
— ASCII Art Archive&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I browsed the &lt;a href=&quot;https://www.asciiart.eu/&quot;&gt;ASCII Art Archive&lt;/a&gt; for inspiration from floppy disks to crowns to computers to abstract imagery and eventually settled on this &lt;a href=&quot;https://www.asciiart.eu/computers/game-consoles&quot;&gt;GameBoy-inspired piece by Hayley Jane Wakenshaw&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; ___
|[_]|
|+ ;| hjw
`---&apos;

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Displaying Static Text in New ZSH Window&lt;/h2&gt;
&lt;p&gt;To display these characters when opening a new terminal window I needed to update my &lt;code&gt;~/.zshrc&lt;/code&gt; file (or include it in a file that my &lt;code&gt;~/.zshrc&lt;/code&gt; loads such as my ZSH theme file) to include the ASCII characters with each Line wrapped in double quotes preceded by the &lt;code&gt;echo&lt;/code&gt; command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; # ~/.zshrc OR your zsh theme file
 echo &quot; ___&quot;
 echo &quot;|[_]|&quot;
 echo &quot;|+ ;| &quot;
 echo &quot;\`---&apos;&quot;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;Callout&amp;gt;
Note: it is necessary to escape characters like backticks with &lt;code&gt;\&lt;/code&gt; otherwise
the ASCII art will truncated prematurely and will not display properly on the
command line.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;After updating my &lt;code&gt;~/.zsrhc&lt;/code&gt; I was able to verify the appearance of the text on start-up by running &lt;code&gt;source ~/.zshrc&lt;/code&gt; within an existing terminal window or when opening a new window.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../images/gameboy-prompt.png&quot; alt=&quot;gameboy prompt output&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Using Variables with Prompt Text&lt;/h2&gt;
&lt;p&gt;In addition to printing out static information, one can also display different information depending on the value of a variable such as time. To determine the current hour one can use the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;date +&quot;%H&quot;
18 # output at 6PM
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I decided based on the current hour to return a different &lt;code&gt;greeting&lt;/code&gt; value by checking whether or not the current hour was &lt;code&gt;-ge&lt;/code&gt; (greater than or equal to) or &lt;code&gt;-le&lt;/code&gt; (less than or equal to) specific time ranges I defined.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Get the current hour in 24-hour format
hour=$(date +&quot;%H&quot;)

# Define time ranges
morning_start=5    # 5 AM
morning_end=11     # 11 AM
afternoon_start=12 # 12 PM
afternoon_end=17   # 5 PM


# Check the time of day and print a message
if [ &quot;$hour&quot; -ge &quot;$morning_start&quot; ] &amp;amp;&amp;amp; [ &quot;$hour&quot; -le &quot;$morning_end&quot; ]; then
    greeting=&quot;Good morning&quot;
elif [ &quot;$hour&quot; -ge &quot;$afternoon_start&quot; ] &amp;amp;&amp;amp; [ &quot;$hour&quot; -le &quot;$afternoon_end&quot; ]; then
    greeting=&quot;Good afternoon&quot;
else
    greeting=&quot;Good evening&quot;
fi

 echo &quot; ___&quot;
 echo &quot;|[_]|&quot;
 echo &quot;|+ ;|  ✦ $greeting, Monica! ✦&quot; # string interpolation
 echo &quot;\`---&apos;&quot;


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above script in my &lt;code&gt;~/.zshrc&lt;/code&gt; outputs a welcome prompt that appears like the below image:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./../../images/welcome-prompt.png&quot; alt=&quot;gameboy prompt output with time of day greeting&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The exact output that is displayed is one of the following messages depending on the time of day:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✦ Good morning, Monica! ✦&lt;/li&gt;
&lt;li&gt;✦ Good afternoon, Monica! ✦&lt;/li&gt;
&lt;li&gt;✦ Good evening, Monica! ✦&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In summary, you can update your &lt;code&gt;~/.zshrc&lt;/code&gt; with &lt;code&gt;echo&lt;/code&gt; to print out messages that only appear when &lt;code&gt;source ~/.zshrc&lt;/code&gt; is initially run or a new terminal window is opened. In addition to always displaying the same welcome message, it can be updated based on various variables such as system values like the current time.&lt;/p&gt;
&lt;p&gt;The full script for my &lt;code&gt;oh-my-zsh&lt;/code&gt; theme, including the welcome message described in this article, is viewable in my &lt;a href=&quot;https://github.com/M0nica/dotfiles/blob/14fefe8aa43d649433c5aba686d11a62862dae6e/.oh-my-zsh/themes/amethyst.zsh-theme#L78-L101&quot;&gt;dotfiles&lt;/a&gt; on GitHub.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Getting Started with SVG Animation</title><link>https://aboutmonica.com/blog/getting-started-with-svg-animation/</link><guid isPermaLink="true">https://aboutmonica.com/blog/getting-started-with-svg-animation/</guid><description>Guide to programmatically animating SVGs</description><pubDate>Tue, 13 Apr 2021 02:48:42 GMT</pubDate><content:encoded>&lt;p&gt;import VanillaCustomSandpack from &quot;../../components/VanillaCustomSandpack.tsx&quot;;&lt;/p&gt;
&lt;p&gt;export const bobaDemoProps = {
html: &lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt; &amp;lt;html lang=&quot;en&quot;&amp;gt;   &amp;lt;head&amp;gt;     &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;     &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&amp;gt;     &amp;lt;title&amp;gt;Bubble Tea Animation&amp;lt;/title&amp;gt;   &amp;lt;/head&amp;gt;   &amp;lt;body&amp;gt;     &amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; id=&quot;bubbletea&quot; viewBox=&quot;0 0 346.68 473.2&quot;&amp;gt;   &amp;lt;defs /&amp;gt;   &amp;lt;g id=&quot;tea&quot; stroke=&quot;#8574a8&quot; stroke-miterlimit=&quot;10&quot; stroke-width=&quot;10&quot;&amp;gt;     &amp;lt;path fill=&quot;#a89bc4&quot; d=&quot;M215.61 415.61c-13.45 21.59-38 47.78-45.61 50-20.94 6.15-107.6-8.78-137-46.11-11.27-14.3-24.81-31.32-27.23-48.54-3.89-27.74 8.33-54.38 9.8-59 17.16-53.69 81.06-197.19 94.31-196.67 56.33 2.23 126.71 34.48 177.65 58.63 9.25 4.39 23.16 9.23 22.63 19.92-2.63 52.72-65.04 174.41-94.55 221.77z&quot; /&amp;gt;     &amp;lt;path fill=&quot;#8574a8&quot; d=&quot;M42.34 252.37c13.31-8.4 31.25-14.24 44.16-5.25 16.05 11.18 16.84 40.35 36.06 44 10.64 2 20.53-5.74 31.18-7.75 18.39-3.47 37.45 14.43 35.16 33-.46 3.73-1.62 7.54-.61 11.16 2 7.21 11.64 9.54 18.73 7.13s12.63-7.9 19-11.83 15-6.16 21.09-1.79c6.88 4.93 6.63 15.29 4.14 23.38-3.36 10.87-6 20.3-12.14 29.88-14 22-19.32 39.92-35.14 60.62-7.8 10.2-21.38 29.53-34 32-7.61 1.51-23.94 1.75-31.5 0-15.12-3.5-32.8-10.79-47.92-14.28-19.18-4.44-36.87-12.44-50.8-26.35-6.41-6.4-26.06-39.3-30.63-47.12-12.88-21.97 24-120.97 33.22-126.8z&quot; opacity=&quot;.22&quot; /&amp;gt;   &amp;lt;/g&amp;gt;   &amp;lt;g id=&quot;cheeks&quot; fill=&quot;#de9bb5&quot; stroke=&quot;#de9bb5&quot; stroke-miterlimit=&quot;10&quot; stroke-width=&quot;10&quot;&amp;gt;     &amp;lt;ellipse cx=&quot;261.24&quot; cy=&quot;323.62&quot; rx=&quot;4&quot; ry=&quot;14.5&quot; transform=&quot;rotate(-68.17 110.06 395.85)&quot; /&amp;gt;     &amp;lt;ellipse cx=&quot;393.21&quot; cy=&quot;376.5&quot; rx=&quot;4.5&quot; ry=&quot;15.5&quot; transform=&quot;rotate(-71.17 245.891 442.235)&quot; /&amp;gt;   &amp;lt;/g&amp;gt;   &amp;lt;path id=&quot;mouth&quot; fill=&quot;none&quot; stroke=&quot;#6a5f71&quot; stroke-miterlimit=&quot;10&quot; stroke-width=&quot;10&quot; d=&quot;M181.82 256.64c-8.55 5.26-19.73 5.71-28.93 1.68a36.68 36.68 0 01-19.54-21.78&quot; /&amp;gt;   &amp;lt;g id=&quot;boba&quot; fill=&quot;#6a5f71&quot; stroke=&quot;#6a5f71&quot; stroke-miterlimit=&quot;10&quot; stroke-width=&quot;10&quot;&amp;gt;     &amp;lt;circle id=&quot;boba-six&quot; cx=&quot;47.65&quot; cy=&quot;303.03&quot; r=&quot;8.95&quot; /&amp;gt;     &amp;lt;circle id=&quot;boba-five&quot; cx=&quot;77.84&quot; cy=&quot;407.44&quot; r=&quot;16&quot; /&amp;gt;     &amp;lt;circle id=&quot;boba-four&quot; cx=&quot;154.25&quot; cy=&quot;415.61&quot; r=&quot;15.75&quot; /&amp;gt;     &amp;lt;ellipse id=&quot;boba-three&quot; cx=&quot;115.13&quot; cy=&quot;434.94&quot; rx=&quot;11.9&quot; ry=&quot;11.5&quot; /&amp;gt;     &amp;lt;ellipse id=&quot;boba-two&quot; cx=&quot;52.35&quot; cy=&quot;359.82&quot; rx=&quot;17&quot; ry=&quot;14.5&quot; /&amp;gt;     &amp;lt;!--     &amp;lt;path id=&quot;boba-one&quot; d=&quot;M181.92 469.3c7.63 1.28 12.49 10.21 10.85 19.95S174.3 468 181.92 469.3z&quot; transform=&quot;translate(-162 -94.94)&quot; /&amp;gt; --&amp;gt;   &amp;lt;/g&amp;gt;   &amp;lt;g id=&quot;eyes&quot; stroke-miterlimit=&quot;10&quot; stroke-width=&quot;10&quot;&amp;gt;     &amp;lt;circle cx=&quot;133.35&quot; cy=&quot;193.96&quot; r=&quot;23.5&quot; fill=&quot;#6a5f71&quot; stroke=&quot;#6a5f71&quot; /&amp;gt;     &amp;lt;circle cx=&quot;215.61&quot; cy=&quot;228.68&quot; r=&quot;23.5&quot; fill=&quot;#6a5f71&quot; stroke=&quot;#6a5f71&quot; /&amp;gt;     &amp;lt;ellipse cx=&quot;224.23&quot; cy=&quot;240.42&quot; fill=&quot;#fff&quot; stroke=&quot;#fff&quot; rx=&quot;1.62&quot; ry=&quot;5.61&quot; /&amp;gt;     &amp;lt;ellipse cx=&quot;142.94&quot; cy=&quot;205.18&quot; fill=&quot;#fff&quot; stroke=&quot;#fff&quot; rx=&quot;1.62&quot; ry=&quot;5.61&quot; /&amp;gt;   &amp;lt;/g&amp;gt;   &amp;lt;g id=&quot;cap&quot; stroke-miterlimit=&quot;10&quot; stroke-width=&quot;10&quot;&amp;gt;     &amp;lt;path fill=&quot;#e5dbdf&quot; stroke=&quot;#e5dbdf&quot; d=&quot;M98.37 79.4c3.74-1.79 20.34-7.92 38.72-10 21.7-2.49 46.27 1.75 56.81 3.31 54.13 8 109.9 38.76 128.83 58.08 4 3.58 5.32 8.7 7.93 13.16 2.41 4.13 11.59 12 11 16.92-1.49 12.16-18.42 29.51-21.17 25.22 0 0-149.18-64-225.64-69.65-9.28-.72-1.23-34.77 3.52-37.04z&quot; /&amp;gt;     &amp;lt;path fill=&quot;#e3e3e3&quot; stroke=&quot;#e3e3e3&quot; d=&quot;M339.21 148.15c.55-16 .22-32.88-8.21-46.42-6-9.67-15.52-16.54-24.78-23.16-12.29-8.78-24.68-17.62-38.36-24-45.7-21.37-102-12.26-144.83 16.68 71.57 26.4 146.57 43.9 216.18 76.9z&quot; /&amp;gt;     &amp;lt;path fill=&quot;#de9bb5&quot; stroke=&quot;#de9bb5&quot; d=&quot;M201 36.34c-.52-5.11-.88-10.32-2.77-15.1-3.44-8.67-12-14.94-21.28-16.05a29.75 29.75 0 00-25.12 9.7c-9 9.93-10.62 24.42-7.31 39.4a353 353 0 0137.56-9.93c6.48-1.34 13.24-2.6 18.65-6.44.37-.72 1.27-1.07.27-1.58z&quot; /&amp;gt;     &amp;lt;ellipse cx=&quot;464.34&quot; cy=&quot;260.32&quot; fill=&quot;#fff&quot; stroke=&quot;#f7f7f7&quot; rx=&quot;3.77&quot; ry=&quot;25&quot; transform=&quot;rotate(-68.97 314.274 330.766)&quot; /&amp;gt;     &amp;lt;circle cx=&quot;319.54&quot; cy=&quot;133.41&quot; r=&quot;5.75&quot; fill=&quot;#fff&quot; stroke=&quot;#fff&quot; /&amp;gt;   &amp;lt;/g&amp;gt; &amp;lt;/svg&amp;gt;   &amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt;&lt;/code&gt;,
css: `:root {
background-color: #f5ecf8;
}&lt;/p&gt;
&lt;p&gt;#bubbletea {
display: block;
margin-left: auto;
margin-right: auto;
width: 400px;
}&lt;/p&gt;
&lt;p&gt;#cheeks {
-webkit-animation: blush 4s infinite;
animation: blush 4s infinite;
}&lt;/p&gt;
&lt;p&gt;@-webkit-keyframes blush {
50% {
stroke: #b7094c;
}
}&lt;/p&gt;
&lt;p&gt;@keyframes blush {
50% {
stroke: #b7094c;
}
}&lt;/p&gt;
&lt;p&gt;#boba-two {
-webkit-animation: jiggle 5s infinite;
animation: jiggle 5s infinite;
}
#boba-three {
-webkit-animation: jiggle 3s infinite;
animation: jiggle 3s infinite;
}
#boba-four {
-webkit-animation: jiggle 4s infinite;
animation: jiggle 4s infinite;
}&lt;/p&gt;
&lt;p&gt;#boba-five {
-webkit-animation: jiggle 2s infinite;
animation: jiggle 2s infinite;
}&lt;/p&gt;
&lt;p&gt;#boba-six {
-webkit-animation: jiggle 6s infinite;
animation: jiggle 6s infinite;
}&lt;/p&gt;
&lt;p&gt;@-webkit-keyframes jiggle {
25% {
-webkit-transform: translateY(3px);
transform: translateY(3px);
}
50% {
-webkit-transform: translateY(6px) scale(0.98);
transform: translateY(6px) scale(0.98);
}&lt;/p&gt;
&lt;p&gt;100% {
-webkit-transform: translateY(4px);
transform: translateY(4px);
}
}&lt;/p&gt;
&lt;p&gt;@keyframes jiggle {
25% {
-webkit-transform: translateY(3px);
transform: translateY(3px);
}
50% {
-webkit-transform: translateY(6px) scale(0.98);
transform: translateY(6px) scale(0.98);
}&lt;/p&gt;
&lt;p&gt;100% {
-webkit-transform: translateY(4px);
transform: translateY(4px);
}
}
`
};&lt;/p&gt;
&lt;p&gt;I attended Cassie Evan&apos;s &lt;a href=&quot;https://www.cassie.codes/speaking/getting-started-with-svg-animation/&quot;&gt;SVG masterclass on getting started with SVG animation&lt;/a&gt; in February and have been enjoying creating and animating more SVGs in recent weeks.&lt;/p&gt;
&lt;h2&gt;Reading and Writing SVGs&lt;/h2&gt;
&lt;p&gt;You may be wondering what exactly are SVGs. &lt;s&gt;SVG&apos;s are magic!&lt;/s&gt; The Scalable Vector Graphics (SVG) format is a human-readable image format. You can create and edit SVGs by writing XML or by editing the file in design software like Adobe Illustrator. SVG images are vector files that are mathematically calculated which means that even if you zoom into an SVG on a website or enlargen it for a billboard it will be just as sharp. In contrast, to vector files, pixel based images (generally photographs) will become blurry at different resolutions.&lt;/p&gt;
&lt;p&gt;One of the cool things about SVGs is that once you understand how to read an SVG file you can change the colors (known as fill or stroke) programmatically.&lt;/p&gt;
&lt;p&gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 1728 500&quot;&amp;gt;
&amp;lt;rect x=&quot;153.55&quot; y=&quot;200&quot; width=&quot;300&quot; height=&quot;300&quot; fill=&quot;#ff499e&quot; /&amp;gt;
&amp;lt;rect x=&quot;519.55&quot; y=&quot;200&quot; width=&quot;300&quot; height=&quot;300&quot; fill=&quot;#99267e&quot; /&amp;gt;
&amp;lt;rect x=&quot;883.41&quot; y=&quot;200&quot; width=&quot;300&quot; height=&quot;300&quot; fill=&quot;#9c5ea8&quot; /&amp;gt;
&amp;lt;rect x=&quot;1263.41&quot; y=&quot;200&quot; width=&quot;300&quot; height=&quot;300&quot; fill=&quot;#798fd4&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/svg&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;four squares generated by SVG&apos;s XML Markup&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The code block below this paragraph represents the four SVG squares above. Each of the squares is a &lt;code&gt;rect&lt;/code&gt; with a different &lt;code&gt;fill&lt;/code&gt; color. Since the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; is the same value of &lt;code&gt;300&lt;/code&gt; these &lt;code&gt;rect&lt;/code&gt;s appear as squares and all of the squares have the same &lt;code&gt;y&lt;/code&gt; coordinate which means they are aligned horizontal but have different &lt;code&gt;x&lt;/code&gt; coordinates. If they had the same &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; coordinate they would overlap with each other. The &lt;code&gt;viewBox&lt;/code&gt; property in the &lt;code&gt;&amp;lt;svg/&amp;gt;&lt;/code&gt; that wraps these components determines how viewed in these shapes appear. Amelia Wattenberger created a great, interactive writeup for &lt;a href=&quot;https://wattenberger.com/guide/scaling-svg&quot;&gt;getting a better understanding of viewBox&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 1728 500&quot;&amp;gt;
    &amp;lt;rect x=&quot;153.55&quot; y=&quot;200&quot; width=&quot;300&quot; height=&quot;300&quot; fill=&quot;#ff499e&quot; /&amp;gt;
  &amp;lt;rect x=&quot;519.55&quot; y=&quot;200&quot; width=&quot;300&quot; height=&quot;300&quot; fill=&quot;#99267e&quot; /&amp;gt;
     &amp;lt;rect x=&quot;883.41&quot; y=&quot;200&quot; width=&quot;300&quot; height=&quot;300&quot; fill=&quot;#9c5ea8&quot; /&amp;gt;
  &amp;lt;rect x=&quot;1263.41&quot; y=&quot;200&quot; width=&quot;300&quot; height=&quot;300&quot; fill=&quot;#798fd4&quot; /&amp;gt;
&amp;lt;/svg&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;ve also written an article about my &lt;a href=&quot;/blog/my-svg-creation-process&quot;&gt;SVG creation process&lt;/a&gt; for more complex SVGs.&lt;/p&gt;
&lt;h2&gt;Animating SVG elements&lt;/h2&gt;
&lt;p&gt;Since the visual properties of SVGs are written in a human readable format we can programmatically change different values of SVGs with CSS or JavaScript to create animations by applying a sequence of styling changes over a duration of time.&lt;/p&gt;
&lt;p&gt;Below is an example of a Bubble Tea SVG that I animated with CSS to make the cheeks blush and move the bubbles around a bit. You can take a peek at the SVG in the HTML tab of the Codepen below to see how I structured the SVG. You may notice that can group different elements in SVG with a &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt; which functions similarly to &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s and can be target programmatically if there is a specific &lt;code&gt;class&lt;/code&gt; or &lt;code&gt;id&lt;/code&gt; associated with a group. Individual SVG shapes and paths can also have their own unique identifiers if you want to target more specifically but so far I&apos;ve found that it&apos;s helpful to animate in groups.&lt;/p&gt;
&lt;p&gt;&amp;lt;VanillaCustomSandpack client:only=&quot;react&quot; {...bobaDemoProps}   showOpenInCodeSandbox/&amp;gt;&lt;/p&gt;
&lt;p&gt;The cheeks in the bubble tea SVG are grouped together with the id &quot;cheeks&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;g id=&quot;cheeks&quot; stroke=&quot;#de9bb5&quot; stroke-miterlimit=&quot;10&quot; stroke-width=&quot;10&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&apos;s a default fill and stroke color set for the &lt;code&gt;#cheeks&lt;/code&gt; based on the original color values in the SVG but we can use CSS to target the &lt;code&gt;#cheeks&lt;/code&gt; and change the colors with the below animation that change the color of its thick stroke by applying a blush &lt;code&gt;animation&lt;/code&gt; every that last a duration of &lt;code&gt;4s&lt;/code&gt; for an &lt;code&gt;infinite&lt;/code&gt; amount of time.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#cheeks {
  /* play the blush animation for 4s and repeat an infinite amount of times */
  animation: blush 4s infinite;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Generally, on production sites you&apos;d want to have more control over &lt;em&gt;when&lt;/em&gt; an animation is running so that it isn&apos;t running continuously as opposed to just when visible or necessary for performance reasons.&lt;/p&gt;
&lt;p&gt;The specifics for &lt;code&gt;blush&lt;/code&gt; to change the fill color are then defined in &lt;code&gt;@keyframes blush&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@keyframes blush {
  50% {
    /* change from original fill color (defined in SVG)
    to #b7094c halfway through the animation */
    stroke: #b7094c;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you view the CodePen you can also see the separate &lt;code&gt;jiggle&lt;/code&gt; animation instances that I applied to each of the individual &lt;code&gt;#boba&lt;/code&gt; at different time points (offsetting the seconds measurement in &lt;code&gt;animation: jiggle 5s infinite&lt;/code&gt;) with the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/transform&quot;&gt;CSS transform property&lt;/a&gt; which allows you to move, scale, rotate and more!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* excerpt of applying the jiggle animation with different timing to different elements*/
#boba-two {
  -webkit-animation: jiggle 5s infinite;
  animation: jiggle 5s infinite;
}
#boba-three {
  -webkit-animation: jiggle 3s infinite;
  animation: jiggle 3s infinite;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Cross-Browser Compatibility&lt;/h2&gt;
&lt;p&gt;You may have noticed some of the CSS in the code examples in this article have prefixe like &lt;code&gt;-webkit&lt;/code&gt;. These are &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Vendor_Prefix&quot;&gt;vendor prefixes&lt;/a&gt; which allows developers to use newer features that aren&apos;t yet considered stable within a specific browser. You can use a website like: https://autoprefixer.github.io/ or a PostCSS tool like https://github.com/postcss/autoprefixer to automate adding these prefixes to your animations to ensure any more experimental features have as much cross-browser support as possible. In addition to using these vendor prefixing tools I&apos;d recommend actually doing cross-browser testing for your animation work and being familiar with &lt;a href=&quot;https://caniuse.com/&quot;&gt;Can I Use&lt;/a&gt; which shows up-to-date information on browser compatibility for specific features.&lt;/p&gt;
&lt;p&gt;The CSS animation examples in this article are relatively simple but more complex animations can be created using similar principles with with CSS animations or with animation libraries like &lt;a href=&quot;https://greensock.com/&quot;&gt;GreenSock Animation Platform (GSAP)&lt;/a&gt;&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Getting Started With Webmention and NextJS</title><link>https://aboutmonica.com/blog/getting-started-with-webmention-next-js/</link><guid isPermaLink="true">https://aboutmonica.com/blog/getting-started-with-webmention-next-js/</guid><description>An overview of setting up Webmention on a NextJS site to collect comments from across the decentralized social web (i.e., Twitter, GitHub, Pinterest, Reddit) in a centralized place.</description><pubDate>Sat, 03 Oct 2020 16:51:30 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;This past week I had the pleasure of being on the &lt;a href=&quot;https://www.learnwithjason.dev/webmention-next-js&quot;&gt;Learn with Jason Show&lt;/a&gt; to show how to add &lt;a href=&quot;https://indieweb.org/Webmention&quot;&gt;Webmention&lt;/a&gt; functionality to a NextJS website. Webmentions let you pull tweets, other blogs, and other activity from around the web into your site? It was fun live pair programming the implementation of webmentions. &lt;a href=&quot;https://www.learnwithjason.dev/webmention-next-js&quot;&gt;Check out the video&lt;/a&gt; or read some of the highlights below!&lt;/p&gt;
&lt;h2&gt;Watch the Learn with Jason Episode&lt;/h2&gt;
&lt;p&gt;Watch the Learn with Jason episode below for more information about Webmentions and view the transcript at https://www.learnwithjason.dev/webmention-next-js:&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe
allowfullscreen=&quot;&quot;
frameborder=&quot;0&quot;
height=&quot;315&quot;
src=&quot;https://www.youtube.com/embed/Ty6tmviF0H0&quot;
width=&quot;500&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;lt;/iframe&amp;gt;
&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;What are Webmentions?&lt;/h2&gt;
&lt;p&gt;Webmention are an IndieWeb specification to allow websites to collect comments from across the decentralized social web (i.e., Twitter, GitHub, Pinterest, Reddit) in a centralized place. A site can be set up to send an outgoing Webmention any time they link to an external website. Websites that receive Webmentions can then decide what content to display and bring the conversation to one place. I&apos;ve mostly used Webmention to bring conversation from Twitter to my personal site.&lt;/p&gt;
&lt;p&gt;Below is an image of how Webmentions appear on one my articles on this site:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/webmention.gif&quot; alt=&quot;scrolling through Webmentions which show the number of RTs, likes and replies. Toggled on and off the view that shows the content of individual replies&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Setting up Webmention Functionality&lt;/h2&gt;
&lt;p&gt;The easiest way to set up your site to start receiving Webmentions is to create an account with &lt;a href=&quot;https://webmention.io/&quot;&gt;webmention.io&lt;/a&gt; to accept Webmentions on your behalf. If you&apos;d like to bring in conversation from social media sites like Twitter, I&apos;d recommend creating an account with &lt;a href=&quot;https://brid.gy/&quot;&gt;bridgy&lt;/a&gt; which will automatically poll social media at set intervals and send any relevant content as a Webmention to &lt;a href=&quot;https://webmention.io/&quot;&gt;webmention.io&lt;/a&gt;. Let&apos;s walkthrough how to set up the necessary accounts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Begin receiving Webmentions with webmention.io&lt;/strong&gt; Go to https://webmention.io/ and enter your website URL. It will prompt you to set up IndieAuth which requires you link to a social media site like Twitter on the homepage of your website with the HTML &lt;code&gt;rel&lt;/code&gt; attribute value set to &lt;code&gt;&quot;me&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;https://twitter.com/waterproofheart&quot; rel=&quot;me&quot;&amp;gt; Twitter&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;This set up is required so that webmention.io can verify that the social media website you use to log in is actually associated with the website you want them to collect Webmentions for.

Once you&apos;ve successfully set up your account you should see the following markup on webmention.io (with _your_ username) under _settings_:

```html
&amp;lt;link rel=&quot;webmention&quot; href=&quot;https://webmention.io/username/webmention&quot; /&amp;gt;
&amp;lt;link rel=&quot;pingback&quot; href=&quot;https://webmention.io/username/xmlrpc&quot; /&amp;gt;
```

This markup should be added to the head of your website and deployed so that Webmentions will officially begin being collected by www.webmention.io.

The above set up allows sites to start sending Webmentions to your site and allows you to be able to start sending Webmentions manually.
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Set up Brid.gy to automatically receive mentions from Twitter and other social media websites&lt;/strong&gt; Social media websites like Twitter generally don&apos;t automatically send Webmention notifications to site despite a lot of link sharing occurring on the site. Therefore, in order to automatically send mentions of your website on Twitter to your site as a Webmention you need to setup &lt;a href=&quot;https://brid.gy/&quot;&gt;Brid.gy&lt;/a&gt;. Brid.gy requires that you authenticate with a social media acocunt and also have a link back to the same domain in your social media profile&apos;s bio section.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;danger&quot;&amp;gt;
{&quot; &quot;}
We learned during the Learn with Jason stream that Brid.gy currently doesn&apos;t
appear to support .app TLD domains and gives a vague error where it doesn&apos;t
recognize these domains when trying to authenticate.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h2&gt;Fetching and Rendering Webmentions&lt;/h2&gt;
&lt;h3&gt;1. Fetch Webmentions&lt;/h3&gt;
&lt;p&gt;We used &lt;code&gt;useEffect&lt;/code&gt; to make a fetch request from webmention.io to retrieve the Webmentions associated with our site and stored its result in state as &lt;code&gt;mentions&lt;/code&gt;. The endpoint that the request is made to is https://webmention.io/api/mentions.jf2?[webmentioniousername]&amp;amp;token=[webmentioniotoken]. This URL should be updated with your webmentionio username and webmentionio token. Note the token only provides read-only access you can choose to keep the token a secret but re-generate as needed.&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;useEffect&lt;/code&gt; only triggers after the initial hydration of a React site, this request only occurs on the client-side and requires JavaScript to be enabled in order to fetch Webmention data. This means that you don&apos;t have to worry about rebuilding your site in order to retrieve the latest Webmention data.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const [mentions, setMentions] = useState([]);
useEffect(() =&amp;gt; {
  fetch(
    &quot;https://webmention.io/api/mentions.jf2?[webmentioniousername]&amp;amp;token=[webmentioniotoken]&quot;,
  )
    .then((response) =&amp;gt; response.json())
    .then((result) =&amp;gt; {
      setMentions(result.children);
    });
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are curious what the shape of the initial object returned by the endpoint looks like some recent Webmentions I received were shaped like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;            {
  &quot;type&quot;: &quot;feed&quot;,
  &quot;name&quot;: &quot;Webmentions&quot;,
  &quot;children&quot;: [
    {
      &quot;type&quot;: &quot;entry&quot;,
      &quot;author&quot;: {
        &quot;type&quot;: &quot;card&quot;,
        &quot;name&quot;: &quot;Debra-Kaye Elliott&quot;,
        &quot;photo&quot;: &quot;https://webmention.io/avatar/pbs.twimg.com/ca78346285aa621eedce7fa271025576c97a19577b4820c9951853d09a829ad8.png&quot;,
        &quot;url&quot;: &quot;https://twitter.com/debrakayelliott&quot;
      },
      &quot;url&quot;: &quot;https://twitter.com/waterproofheart/status/1312230169491447815#favorited-by-261719963&quot;,
      &quot;published&quot;: null,
      &quot;wm-received&quot;: &quot;2020-10-03T17:29:46Z&quot;,
      &quot;wm-id&quot;: 864384,
      &quot;wm-source&quot;: &quot;https://brid-gy.appspot.com/like/twitter/waterproofheart/1312230169491447815/261719963&quot;,
      &quot;wm-target&quot;: &quot;https://www.aboutmonica.com/blog/set-yourself-up-for-success-open-source-contributions/&quot;,
      &quot;like-of&quot;: &quot;https://www.aboutmonica.com/blog/set-yourself-up-for-success-open-source-contributions/&quot;,
      &quot;wm-property&quot;: &quot;like-of&quot;,
      &quot;wm-private&quot;: false
    },
    {
      &quot;type&quot;: &quot;entry&quot;,
      &quot;author&quot;: {
        &quot;type&quot;: &quot;card&quot;,
        &quot;name&quot;: &quot;Anders Schneiderman&quot;,
        &quot;photo&quot;: &quot;https://webmention.io/avatar/pbs.twimg.com/37890c358e8ff1f667c57d89959e583af37ef66d39b6d3c372d9b7988f5a377f.jpg&quot;,
        &quot;url&quot;: &quot;https://twitter.com/raschneiderman&quot;
      },
      &quot;url&quot;: &quot;https://twitter.com/waterproofheart/status/1312230169491447815#favorited-by-270006742&quot;,
      &quot;published&quot;: null,
      &quot;wm-received&quot;: &quot;2020-10-03T15:08:41Z&quot;,
      &quot;wm-id&quot;: 864330,
      &quot;wm-source&quot;: &quot;https://brid-gy.appspot.com/like/twitter/waterproofheart/1312230169491447815/270006742&quot;,
      &quot;wm-target&quot;: &quot;https://www.aboutmonica.com/blog/set-yourself-up-for-success-open-source-contributions/&quot;,
      &quot;like-of&quot;: &quot;https://www.aboutmonica.com/blog/set-yourself-up-for-success-open-source-contributions/&quot;,
      &quot;wm-property&quot;: &quot;like-of&quot;,
      &quot;wm-private&quot;: false
    },
    ]}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also view the shape of the data by going directly to https://webmention.io/api/mentions.jf2?[webmentioniousername]&amp;amp;token=[webmentioniotoken].&lt;/p&gt;
&lt;h3&gt;2. Render Webmentions&lt;/h3&gt;
&lt;p&gt;Once we have the &lt;code&gt;mentions&lt;/code&gt; stored in state from the request to webmention.io we can map through the mentions and render the a link to the Webmention author&apos;s original post, their photo, name and the content of their Webmention.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  mentions.map((mention) =&amp;gt; (
    &amp;lt;div&amp;gt;
      &amp;lt;a href={mention.author.url}&amp;gt;
        &amp;lt;img
          style={{ width: 100 }}
          src={mention.author.photo}
          alt={mention.author.name}
        /&amp;gt;
      &amp;lt;/a&amp;gt;
      &amp;lt;div dangerouslySetInnerHTML={{ __html: mention.content.html }} /&amp;gt;
    &amp;lt;/div&amp;gt;
  ));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Final Code&lt;/h2&gt;
&lt;p&gt;Below is more complete version of the above code with some styling. Check out the demo repo for the application I created with Jason on Learn with Jason here: https://github.com/jlengstorf/next-netlify-webmention&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useEffect, useState } from &quot;react&quot;;

const Index = ({ ...props }) =&amp;gt; {
  const [mentions, setMentions] = useState([]);
  useEffect(() =&amp;gt; {
    /* add your webmentionio username and webmentionio token.
        Note the token only provides read-only access
        you can choose to keep the token a secret but re-generate as needed */
    fetch(
      &quot;https://webmention.io/api/mentions.jf2?[webmentioniousername]&amp;amp;token=[webmentioniotoken]&quot;,
    )
      .then((response) =&amp;gt; response.json())
      .then((result) =&amp;gt; {
        setMentions(result.children);
      });
  }, []);
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Layout&amp;gt;
        &amp;lt;h2&amp;gt;Discussion about this site on the web&amp;lt;/h2&amp;gt;
        &amp;lt;p&amp;gt;This uses Webmention&amp;lt;/p&amp;gt;
        {mentions.map((mention) =&amp;gt; (
          &amp;lt;div
            style={{
              display: &quot;grid&quot;,
              gap: &quot;1rem&quot;,
              gridTemplateColumns: &quot;100px 1fr&quot;,
            }}
          &amp;gt;
            &amp;lt;a href={mention.author.url}&amp;gt;
              &amp;lt;img
                style={{ width: 100 }}
                src={mention.author.photo}
                alt={mention.author.name}
              /&amp;gt;
            &amp;lt;/a&amp;gt;
            &amp;lt;div dangerouslySetInnerHTML={{ __html: mention.content.html }} /&amp;gt;
          &amp;lt;/div&amp;gt;
        ))}
      &amp;lt;/Layout&amp;gt;
      &amp;lt;style jsx&amp;gt;{`
        .title {
          margin: 1rem auto;
          font-size: 3rem;
        }
      `}&amp;lt;/style&amp;gt;
    &amp;lt;/&amp;gt;
  );
};

export default Index;
&lt;/code&gt;&lt;/pre&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>git is hard, but time travel in git is easy.</title><link>https://aboutmonica.com/blog/git-time-travel/</link><guid isPermaLink="true">https://aboutmonica.com/blog/git-time-travel/</guid><description>This article explains how it&apos;s possible to commit code that is dated in the future.</description><pubDate>Sun, 24 Mar 2019 02:43:13 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/media/monicadancing.png&quot; alt=&quot;monica dancing - in space?&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This article will explain how it&apos;s possible to commit code that is dated in the future.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/futuregitcommit.png&quot; alt=&quot;Committing changes in the future&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I always (wrongly) assumed that GitHub determined the time a commit was made based on their website&apos;s server time. I made this assumption because for certain user actions GitHub uses their server time, for example when a pull request is opened. However, one day I was debugging a calendar issue that only appeared for certain dates. I was able to reproduce and resolve the bug locally by changing my computer time. I then pushed the git branch with my solution up to the remote git repository on GitHub for code review and realized that GitHub commits reflect the date set in the local version of the git repository which is determined by your computer&apos;s system time and not their servers.&lt;/p&gt;
&lt;p&gt;GitHub, GitLab and BitBucket are git repository hosting services, that provide a centralized place to host remote versions of git repositories. These websites have additional functionality build on top of git to make the software development process more collaborative through things such as code review interfaces and other features that make it more of a social network than just a command line interface.&lt;/p&gt;
&lt;p&gt;If you want to intentionally for whatever reason commit something in the future, do you have to manually adjust your computer&apos;s time? No, you can simply pass git environment variables to set the date and time.&lt;/p&gt;
&lt;p&gt;The following line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GIT_AUTHOR_DATE=&quot;Wed Mar 27 15:12:30 2019 -0700 GIT_COMMITTER_DATE=&quot;Wed Mar 27 15:12:30 2019 -0700”
git commit -m “future commit”
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will date a commit as March 27, 2019 whether the current date is before or after March 27th. If you send in a patch to a project and one of the core members applies the patch, both of you get credit — you as the GIT_AUTHOR, and the core maintainer as the GIT_COMMITTER.&lt;/p&gt;
&lt;p&gt;You can &lt;a href=&quot;https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History&quot;&gt;rewrite your git history&lt;/a&gt; and apply changes to any past commit as well.&lt;/p&gt;
&lt;p&gt;This post is based off of a lightning talk that I gave with Women Who Code NYC in March 2019. The slides can be viewed &lt;a href=&quot;https://noti.st/monica/6DejHg/git-is-hard-but-time-traveling-in-git-isnt&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Give Your CSS Superpowers with CSS Variables</title><link>https://aboutmonica.com/blog/give-your-css-superpowers-with-css-variables/</link><guid isPermaLink="true">https://aboutmonica.com/blog/give-your-css-superpowers-with-css-variables/</guid><description>This post will introduce what CSS variables are, how to use them and why to consider using them.</description><pubDate>Fri, 22 May 2020 13:41:29 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;h2&gt;What are CSS variables?&lt;/h2&gt;
&lt;p&gt;CSS variables or CSS custom properties are a way to declare reusable values within a CSS project. CSS variables can greatly reduce the repetition of code and allow us to reference more semantically meaningful names throughout our stylesheets like &quot;--accent-color&quot; versus the actual value they represent &quot;#b5838d&quot;.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout&amp;gt;
{&quot; &quot;}
Most modern browsers support CSS variables. If you need to know whether or not
CSS variables are supported in a particular browser without a polyfill it&apos;s a
best-practice to reference browser-compatibility specifications on sites like{&quot; &quot;}
&amp;lt;a href=&quot;https://caniuse.com/#feat=css-variables&quot;&amp;gt;Can I use?&amp;lt;/a&amp;gt; or{&quot; &quot;}
&amp;lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web&quot;&amp;gt;MDN web docs&amp;lt;/a&amp;gt;.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/css-variable-browser-support.png#center&quot; alt=&quot;screenshot of CSS variable compatibility&quot; /&gt;
Current CSS Variables Browser Support (Source: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties&quot;&gt;MDN Web Docs&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;How to declare CSS variables&lt;/h2&gt;
&lt;p&gt;In order to use CSS variables you should create a CSS custom property, which is a string prepended with a double hyphen &lt;code&gt;--&lt;/code&gt;. These properties are scoped and available based on their selector. Generally, it is recommended to make the selector the pseudoselector &lt;code&gt;:root&lt;/code&gt; so that it will apply throughout an entire site. If you select a more specific selector then the scope of where you can access the value of the variable changes accordingly.&lt;/p&gt;
&lt;p&gt;Below is an example showing 5 different CSS variables being declared to represent different colors within a stylesheet.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:root {
  --white: #fff;
  --accent-color: #b5838d;
  --dark-accent-color: #a6757f;
  --main-color: #6d6875;
  --dark-main-color: #56505f;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to access the these CSS values throughout our CSS we can use the &lt;code&gt;var()&lt;/code&gt; method like &lt;code&gt;var(--accent-color)&lt;/code&gt;. The following example selects the element with the &lt;code&gt;signup&lt;/code&gt; class and changes its background-color to &lt;code&gt;#b5838d&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.signup {
  background-color: var(--accent-color);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the value of &lt;code&gt;--accent-color&lt;/code&gt; is being set with a CSS variable if we later decide to change the value of &lt;code&gt;var(--accent-color)&lt;/code&gt; we only have to change it in one place, where the variables value is declared. Once the value of the variable is updated its new value will automatically be reflected everywhere &lt;code&gt;var(--accent-color)&lt;/code&gt; is referenced. Being able to change the color in one place for a multitude of unrelated elements that are only coupled by color is powerful.&lt;/p&gt;
&lt;h2&gt;The Power of CSS Variables&lt;/h2&gt;
&lt;p&gt;CSS variables allow you to quickly play around with a color scheme and tweak the same color everywhere it is referenced without having to do a find and replace. Additionally, setting up CSS variables can improve the developer experience when creating color schemes, as you can switch the variable declaration values between themes without having to explicitly change the values within the rest of the stylesheet.&lt;/p&gt;
&lt;p&gt;The two screenshots below show the difference in appearance when one line in the CSS file is changed from &lt;code&gt;--accent-color: orange;&lt;/code&gt; to &lt;code&gt;--accent-color: #b5838d;&lt;/code&gt; which is used in two completely separate places -- on the card&apos;s border-top property and the background color for the signup button. This can be very useful as stylesheets grow and values are re-used throughout.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;--accent-color: orange;&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;--accent-color: #b5838d;&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;/media/orange-css-var-example.png#center&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;/media/pink-css-var-example.png#center&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Explore CSS variables!&lt;/h2&gt;
&lt;p&gt;Play around with the CSS variable values by &lt;a href=&quot;https://codepen.io/M0nica/pen/jObpJma&quot;&gt;editing the below Codepen&lt;/a&gt; to see CSS variables in action!&lt;/p&gt;
&lt;p&gt;https://codepen.io/M0nica/pen/jObpJma&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Grey&apos;s Anatomy Lorem Ipsum Generator Tutorial</title><link>https://aboutmonica.com/blog/greys-anatomy-lorem-ipsum-generator/</link><guid isPermaLink="true">https://aboutmonica.com/blog/greys-anatomy-lorem-ipsum-generator/</guid><description>This tutorial is going to walk through how to create a Grey’s Anatomy or other-themed Lorem Ipsum generator with React and Netlify Functions</description><pubDate>Sun, 02 Jun 2019 11:43:13 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/media/lorem-ipsum-generator-screenshot.png&quot; alt=&quot;screenshot of grey&apos;s anatomy lorem ipsum generator&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This tutorial is going to walk through how to create a Grey’s Anatomy or other-themed Lorem Ipsum generator. I’m a huge fan of the show, Grey’s Anatomy (and Shonda Rhimes in general) for a while so I was overdue for creating a Grey’s Anatomy-themed Lorem Ipsum generator. Lorem Ipsum is generated, nonsense text that is used in design and publishing as placeholder text. Traditionally, Lorem Ipsum is written with Latin words but there are variations that involve non-Latin words as well.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/shonda-rhimes-enthusiast.png&quot; alt=&quot;This tweet thread about being a Shonda Rhimes enthusiast https://twitter.com/waterproofheart/status/408430997517393920&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The live version of the Grey&apos;s Anatomy Lorem Ipsum Generator website can be viewed &lt;a href=&quot;https://greysanatomyloremipsum.netlify.com/&quot;&gt;here&lt;/a&gt; and the code for this tutorial can be viewed
&lt;a href=&quot;https://github.com/M0nica/greys-anatomy-lorem-ipsum-generator/tree/loremIpsumTutorial&quot;&gt;on Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here’s an example of the type of text that is generated by the Grey&apos;s Anatomy Lorem Ipsum generator:&lt;/p&gt;
&lt;p&gt;“real hospital Dr. Cristina Yang well a trauma surgical fellow badly chief of pediatric surgery George Ellis orthopedic surgeon Dr. Shane Ross approximately Dr. Callie Torres exactly Dr. Izzie Stevens Izzie chief of plastic surgery an anesthesiologist Meredith’s father Zola Seattle Erica certainly Meredith Dr. Shane Ross basically basically Tucker maybe Dr. Virginia Dixon whoever Chief Richard Sadie significantly nicely Arizona real very Dr. Colin Marlowe Zola mostly things Dr. Teddy Altman rather Arizona Dr. Teddy Altman Seattle Dr. Izzie Stevens my person”&lt;/p&gt;
&lt;p&gt;The above paragraph makes absolutely no sense but if you’re familiar with Grey’s Anatomy there are some familiar terms baked in there.&lt;/p&gt;
&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;p&gt;To get the most out of this tutorial you should:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Have familiarity with git and JavaScript&lt;/li&gt;
&lt;li&gt;Have a GitHub or GitLab account (in order to use Netlify functions).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Set Up&lt;/h2&gt;
&lt;p&gt;In order to jump-start this project, I used Netlify’s &lt;a href=&quot;https://github.com/netlify/create-react-app-lambda&quot;&gt;GitHub - netlify/create-react-app-lambda&lt;/a&gt; starter. Netlify is a hosting service that specializes in hosting JAMstack websites, as of writing this tutorial, has a pretty comprehensive free tier(&lt;a href=&quot;https://www.netlify.com/pricing/&quot;&gt;Plans and Pricing | Netlify&lt;/a&gt;) that integrates well with deploying GitHub projects to the world wide web. JAMstack is used to describe static websites that are compromised of JavaScript, APIs, and Markup. You can learn more about what is or isn’t considered JAMstack here &lt;a href=&quot;https://jamstack.org&quot;&gt;JAMstack | JavaScript, APIs, and Markup&lt;/a&gt; ,&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;create-react-app-lambda&lt;/code&gt; repository integrates Netlify’s functions with &lt;code&gt;create-react-app&lt;/code&gt; which means out of the box you can deploy a React application that calls, Serverless functions, which you may know as Amazon Web Services (AWS) Lambdas. These functions can make asynchronous calls and process data but they must be stateless, meaning that given the same inputs the output will be the same. With Netlify you can deploy and manage AWS Lambda’s technology without an AWS account. You can read more about Serverless Lambda Functions on Netlify here: &lt;a href=&quot;https://www.netlify.com/docs/functions/&quot;&gt;Functions | Netlify&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let’s get started by copying over &lt;code&gt;create-react-app-lambda&lt;/code&gt; . Click on the “Deploy to Netlify” button in the README.
&lt;img src=&quot;/media/deploy-to-netlify.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This one-click button allows us to set up React + Netlify Functions without having to do heavy lifting. Essentially when you click the button you are creating a new site in Netlify and a connected GitHub repository. It takes a few minutes for the GitHub repository to populate with the files but once it is done populating you should be able to visit Netlify and view your site. If you’d like a better understanding of why things were set up the way they were in the &lt;code&gt;create-react-lambda-app&lt;/code&gt; then check out &lt;a href=&quot;https://www.youtube.com/watch?v=3ldSM98nCHI&quot;&gt;this video&lt;/a&gt; by Shawn Wang (swyx) for more context.&lt;/p&gt;
&lt;p&gt;After clicking the &quot;Deploy to Netlify&quot; button you should see something like this:
&lt;img src=&quot;/media/welcome-to-netlify.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Note: Comprehensive instructions for interacting with the &lt;code&gt;create-react-lambda-app&lt;/code&gt; project live in the README. I will highlight the most important pieces to interact with the app but encourage exploring the README for more information and to further enhance the application.&lt;/p&gt;
&lt;p&gt;So once the repository that you created in Netlify is populated with files from &lt;code&gt;create-react-app-lambda&lt;/code&gt; you should &lt;code&gt;git clone&lt;/code&gt; the repo. The repository that you are cloning should be named after the repo that you created, i.e., &lt;code&gt;git clone git@github.com:your_username/app-just-created&lt;/code&gt;. You may have to refresh the repository page before the files are populated.
&lt;img src=&quot;/media/git-clone-screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once the project is set up you should &lt;code&gt;cd&lt;/code&gt; into the project directory and run &lt;code&gt;yarn&lt;/code&gt; to install all of the dependencies. You can run &lt;code&gt;yarn test&lt;/code&gt; to ensure that all of the tests are passing and the project was set up properly.&lt;/p&gt;
&lt;p&gt;The Netlify functions should be created in &lt;code&gt;src/lambda&lt;/code&gt;. If you look in that folder there are two sample functions &lt;code&gt;async-dadjoke.js&lt;/code&gt; and &lt;code&gt;hello.js&lt;/code&gt;. For our use case we don’t need an &lt;code&gt;async&lt;/code&gt; function so let’s look at the &lt;code&gt;hello.js example&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;hello.js&lt;/code&gt; we are getting the &lt;code&gt;queryStringParameters&lt;/code&gt; from the event in order to log them. &lt;code&gt;queryStringParameters&lt;/code&gt; can be accessed from the event like &lt;code&gt;event.queryStringParameters&lt;/code&gt; or by destructuring the event object like &lt;code&gt;const {queryStringParameters} = event&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function handler(event, context, callback) {

  console.log(“queryStringParameters”, event.queryStringParameters)

  callback(null, {

    statusCode: 200,

    body: JSON.stringify({ msg: “Hello, World!” })

  })

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every Lambda function has a handler function. This handler function can take event, context, and callback. The event is based on what the endpoint received when the request was made it can include things like cookie information, headers, queryStringParameters, etc. The context object provides additional insight regarding a Lambda’s execution. You can learn more in the AWS docs here:
&lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html&quot;&gt;AWS Lambda Function Handler in Node.js - AWS Lambda&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Verify Set Up&lt;/h2&gt;
&lt;p&gt;In order to tests the functionality of the endpoint we should run &lt;code&gt;yarn start:lambda&lt;/code&gt;, in the directory we just clone, which will run all of your Lambda functions. And then you can visit &lt;code&gt;http://localhost:9000/hello&lt;/code&gt; or &lt;code&gt;/&lt;/code&gt; whatever the name of your function is. On this page, you should see: &lt;code&gt;{“msg”:”Hello, World!”}&lt;/code&gt; since that is the body of the endpoint’s response.&lt;/p&gt;
&lt;h2&gt;Calling the Netlify function from React&lt;/h2&gt;
&lt;p&gt;Next, I would recommend customizing the naming conventions in the project to better fit your needs. I renamed &lt;code&gt;hello&lt;/code&gt; to &lt;code&gt;generate-lorem-ipsum&lt;/code&gt;, so first I rename the&lt;code&gt;hello.js&lt;/code&gt; file to &lt;code&gt;generate-lorem-ipsum&lt;/code&gt; and then in the &lt;code&gt;LambdaDemo&lt;/code&gt; component in &lt;code&gt;app.js&lt;/code&gt; , I replaced the call to &lt;code&gt;hello&lt;/code&gt; on button click to &lt;code&gt;generate-lorem-ipsum&lt;/code&gt; . While I was there I also deleted the button related to &lt;code&gt;async-dadjoke&lt;/code&gt; and removed the associated file. So I went from&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;button onClick={this.handleClick(&quot;hello&quot;)}&amp;gt;{loading ? &quot;Loading...&quot; : &quot;Call Lambda&quot;}
&amp;lt;/button&amp;gt;

&amp;lt;button onClick={this.handleClick(&quot;async-dadjoke&quot;)}&amp;gt;{loading ? &quot;Loading...&quot; : &quot;Call Async Lambda&quot;}
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;button onClick={this.handleClick(&quot;generate-lorem-ipsum&quot;)} className=“button”&amp;gt;
            {loading ? “Loading…” : “Generate Lorem Ipsum”}
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The button is calling the Netlify function on click, you can look at the &lt;code&gt;handleClick for more information&lt;/code&gt; about what happens when you click on the button, initially It returns loading as the state and then eventually the response.msg.&lt;/p&gt;
&lt;p&gt;Note: I updated references to &lt;code&gt;LambdaDemo()&lt;/code&gt; to &lt;code&gt;LambdaCall()&lt;/code&gt;in &lt;code&gt;App.js&lt;/code&gt; because it’s show time!&lt;/p&gt;
&lt;p&gt;In order to check the lambda is still being called and returning a response you should run &lt;code&gt;yarn start&lt;/code&gt; (while &lt;code&gt;yarn start:lambda&lt;/code&gt; is running in another terminal window). You should be able to visit http://localhost:3000 and see the React app with a “Generate Lorem Ipsum” button. If you click then the words “hello world” should appear on the site below the button.&lt;/p&gt;
&lt;h2&gt;Adding Lorem Ipsum Logic&lt;/h2&gt;
&lt;p&gt;So now we need to edit the &lt;code&gt;generate-lorem-ipsum.js&lt;/code&gt; file so that it returns Lorem Ipsum and not “Hello World”. We will achieve this by creating a word bank of terms in &lt;code&gt;words.js&lt;/code&gt; and the following functions in the &lt;code&gt;generate-lorem-ipsum.js&lt;/code&gt; file:
&lt;code&gt;handler()&lt;/code&gt;
&lt;code&gt;generateLoremIpsum()&lt;/code&gt;
&lt;code&gt;generateWords()&lt;/code&gt;
&lt;code&gt;generateParagraphs()&lt;/code&gt;
&lt;code&gt;getRandomInt()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;handler()&lt;/code&gt; is the entry point into the file, therefore any functions that need to execute should either be called in the &lt;code&gt;handler()&lt;/code&gt; or called by the functions that the &lt;code&gt;handler()&lt;/code&gt; calls. First, we destructure the event to get the &lt;code&gt;queryStringParameters&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example, if someone calls our endpoint with the following query string parameter
&lt;code&gt;/generate-lorem-ipsum?paragraphs=4&amp;amp;words=0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Then we would de-structure the event object to determine that the endpoint was requested to return 4 paragraphs and 0 words.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const event = {
queryStringParameters: {
paragraphs: “4”,
words: “0”
}
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The handler will call a function &lt;code&gt;generateLoremIpsum&lt;/code&gt; to actually handle generating the text, it takes in whether or not multiple paragraphs or just words should be returned. By default if there are no &lt;code&gt;queryStringParameters&lt;/code&gt; it will return 4 generated paragraphs.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;handler()&lt;/code&gt; could end up looking something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function handler(event, context, callback) {
  const { queryStringParameters } = event;
  const { paragraphs = 0, words = 0 } = queryStringParameters;

  let isParagraph = Boolean(paragraphs);
  let count;

  if (paragraphs &amp;gt; 1) {
    count = paragraphs;
  } else if (words &amp;gt; 0) {
    count = words;
  } else {
    isParagraph = true;
    count = 4;
  }

  let response;
  try {
    response = isParagraph
      ? generateLoremIpsum(isParagraph, count).join(&quot; &quot;)
      : generateLoremIpsum(isParagraph, count);
  } catch (error) {
    console.log(error);
  }

  callback(null, {
    statusCode: 200,
    body: JSON.stringify({ msg: response }),
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example &lt;code&gt;generateLoremIpsum()&lt;/code&gt; is a function called by the &lt;code&gt;handler()&lt;/code&gt; and used as a fork in the road to determine if multiple paragraphs should be generated or just one based on if &lt;code&gt;isParagraph&lt;/code&gt; is true or false.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function generateLoremIpsum(isParagraph, count) {
  if (isParagraph) {
    console.log(`Trying to construct ${count} paragraphs`);
    return generateParagraphs(count);
  } else {
    console.log(`Trying to return ${count} words`);
    return generateWords(count);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we are generating a single paragraph the &lt;code&gt;generateWords()&lt;/code&gt; will be called directly. This function creates an array of random words (based on getting a randomInt and adding the word at that index to the array until we reach the desired &lt;code&gt;wordCount&lt;/code&gt;. In order to format the words once we have all of the words the words are formatted like &lt;code&gt;const formattedWords = &amp;lt;p&amp;gt;${words.join(&quot; &quot;)}&amp;lt;/p&amp;gt;&lt;/code&gt;;` in order to easily be able to transform the function’s response into an HTML paragraph later.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function generateWords(wordCount) {
  let words = [];
  console.log(wordCount);

  for (var i = 0; i &amp;lt; wordCount; i++) {
    words.push(WORDS[getRandomInt()]);
  }
  const formattedWords = `&amp;lt;p&amp;gt;${words.join(&quot; &quot;)}&amp;lt;/p&amp;gt;`;

  return formattedWords;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The random int in the &lt;code&gt;generateWords()&lt;/code&gt; function is calculated with the following function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function getRandomInt() {
  return Math.floor(Math.random() * Math.floor(WORDS.length));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the case where we are generating paragraphs we need the function &lt;code&gt;generateParagraphs()&lt;/code&gt;. This function will generate X paragraphs with 50 words until it reaches the paragraphCount. It does this by calling the &lt;code&gt;generateWords()&lt;/code&gt; function X times where X equals the &lt;code&gt;paragraphCount&lt;/code&gt; that was passed in.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function generateParagraphs(paragraphCount) {
  let paragraphs = [];
  for (var I = 0; I &amp;lt; paragraphCount; I++) {
    paragraphs.push(generateWords(50));
  }
  return paragraphs;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order for any of the above to work we need a word bank to draw from. At the top of &lt;code&gt;generate-lorem-ipsum.js&lt;/code&gt; we should &lt;code&gt;import { WORDS } from &quot;./words&quot;;&lt;/code&gt; and then in the same directory create a file called &lt;code&gt;words.js&lt;/code&gt;. In &lt;code&gt;words.js&lt;/code&gt; we are going to create an array called &lt;code&gt;WORDS&lt;/code&gt; and export it so that other functions can read it.&lt;/p&gt;
&lt;p&gt;I created an array with just filler words( &lt;code&gt;fillerWords&lt;/code&gt;) and another with Grey’s Anatomy and medical terms (&lt;code&gt;greysAnatomyWords&lt;/code&gt;).
Used ES6 spread operator to combine the arrays into one. &lt;code&gt;export const WORDS = […greysAntomyWords, …fillerWords];&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The full file should look something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const greysAnatomyWords = [&quot;Meredith Grey&quot;, &quot;Shonda Rhimes&quot;, &quot;Epi&quot;, &quot;Cardio&quot;];

const fillerWords = [
  &quot;actual&quot;,
  &quot;actually&quot;,
  &quot;amazing&quot;,
]`export const WORDS = […greysAntomyWords, …fillerWords];`;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Methodology&lt;/h2&gt;
&lt;p&gt;In a full blown project we would need a lot of words or else it will be too repetitive. If you want to create another type of themed Lorem Ipsum then you would replace the &lt;code&gt;greysAnatomyWords&lt;/code&gt; with words from a theme of your choice.&lt;/p&gt;
&lt;p&gt;For the grey’s anatomy words I brainstormed and also found some lists like this &lt;a href=&quot;https://www.sheknows.com/parenting/articles/1008783/greys-anatomy-baby-names/&quot;&gt;Grey’s Anatomy Baby Names article&lt;/a&gt; that I was able to re-purpose. The process of cleaning up the data and formatting into valid JSON can be a bit tedious, I did find and replace for formatting where I could and some manually updating as needed. This allowed me to get as much data as possible with minimal effort (I.e., automating the scraping of terms). I ended up with about 140 terms for my generator but if I needed a larger data set then it may have made sense to consider exploring a tool like BeautifulSoup or Selenium to automate scraping a data source and saving that data into a valid JS file.&lt;/p&gt;
&lt;p&gt;Note: Depending on how repetitive data is can create Map or filter to make sure that the array of words only has unique values.&lt;/p&gt;
&lt;p&gt;I decided to add in filler words so that the generated Lorem Ipsum text would have a healthy mix of verbs and adverbs in addition to the themed. I Google’d filler text and found the following repository, I copied the filler words from there: fillers_data.txt at master · words_fillers · GitHub&lt;a href=&quot;https://github.com/words/fillers/blob/master/data.txt&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now if you run &lt;code&gt;yarn start:lambda&lt;/code&gt; and visit &lt;code&gt;http://localhost:9000/generate-lorem-ipsum/&lt;/code&gt;you should see 4 generated paragraphs of lorem ipsum returned. It should look something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;“msg”:”&amp;lt;p&amp;gt;especially surgicial mostly try hospital anyway Seattle Dr. Lucy Fields Alex a trauma surgical fellow Dr. Mark Sloan my person heavily wherever Theodora Duquette Dr. Virginia Dixon cried a nurse a neurosurgeon really Margaret Dr. Mark Sloan Dr. April Kepner Meredithâ€™s father literally Dr. Alex Karev Dr. Izzie Stevens an anesthesiologist Denny much necessarily Surgery a trauma surgical fellow surely hardly Owen rather Shepherd totally cried chief of pediatric surgery Theodora Dr. Robert Stark Olivia an anesthesiologist get her up to CT actually Cristina Dr. Finn Dandridge&amp;lt;/p&amp;gt; &amp;lt;p&amp;gt;Tucker Virginia Callie Torres mostly hardly Maggie Maggie get her up to CT hardly get him up to CT quite stuff Dr. Mark Sloan whenever Dr. Richard Webber try George amazing Dr. Sydney Heron Dr. Jackson Avery actual quite nicely Richard stuff might Dr. Owen Hunt get her up to CT orthopedics Yang obviously mostly intubate her wherever orthopedic surgeon typically Margaret Karev effectively Alex Dr. Mark Sloan Seattle Dr. Alex Karev push one of epi try practically Dr. Alex Karev intubate him so&amp;lt;/p&amp;gt; &amp;lt;p&amp;gt;start significantly start fairly get him up to CT slightly Dr. Alex Karev chief of plastic surgery slightly Dr. Robert Stark Meredithâ€™s mother Norman actually completely Izzie Dr. Mark Sloan particularly Alex basically Adele clearly like usually Seattle Dr. Alex Karev typically chief of plastic surgery get him up to CT essentially ultimately my person exactly Norman specifically Virginia effectively O’Malley intubate her Virginia Tucker my person a surgery resident largely most a veterinarian basically she’s in V-Fib try simply Seattle&amp;lt;/p&amp;gt; &amp;lt;p&amp;gt;heavily Dr. Callie Torres essentially Dr. Jackson Avery whoever a nurse Dr. Mark Sloan definitely my person Olivia Harper Dr. Alex Karev essentially approximately generally my person exactly Dr. Miranda Bailey Dr. Preston Burke right a plastic surgeon Norman Theodora basically a cardiac surgeon chief of plastic surgery chief of plastic surgery essentially Dr. Jackson Avery Tyler Christian much seriously Meredithâ€™s mother slightly easily Mark my person maybe Mercy West Alex Erica Derek certainly badly rather hospital Denny Dr. Norman Shales Dr. Lexie Grey significantly Dr. Jackson Avery&amp;lt;/p&amp;gt;”
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Formatting Response&lt;/h2&gt;
&lt;p&gt;This blob of text is not formatted yet but we will worry about that in the React app. So let’s switch back to &lt;code&gt;app.js&lt;/code&gt; . We are already printing the messages if you run &lt;code&gt;yarn start&lt;/code&gt; and go to &lt;code&gt;localhost:3000&lt;/code&gt; you will see the same body text we just saw. Let’s use a library to format the response as actual HTML.&lt;/p&gt;
&lt;p&gt;We will use the “html-react-parser” package. It can be installed by running &lt;code&gt;yarn add html-react-parser&lt;/code&gt; and then add this &lt;code&gt;import parse from “html-react-parser”;&lt;/code&gt; to the top of your &lt;code&gt;app.js&lt;/code&gt; file in order to import it. Instead of just returning the &lt;code&gt;msg&lt;/code&gt; in &lt;code&gt;LambdaCall&lt;/code&gt; let’s replace &lt;code&gt;msg&lt;/code&gt; with &lt;code&gt;{msg &amp;amp;&amp;amp; parse(msg)}&lt;/code&gt;. This says to parse the HTML of the &lt;code&gt;msg&lt;/code&gt; and return it whenever there is a &lt;code&gt;msg&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This part of the tutorial is choose-your-own-adventure. After setting up the Lorem Ipsum text generation logic. I went on to prettify the site to make it look more customized than the standard &lt;code&gt;create-react-app&lt;/code&gt; boilerplate. One of the tools I used was this &lt;a href=&quot;http://buttonoptimizer.com/&quot;&gt;fancy button generator&lt;/a&gt;. I recommend playing around with the CSS until it matches the aesthetic that you want.&lt;/p&gt;
&lt;h3&gt;Deploy&lt;/h3&gt;
&lt;p&gt;Thanks to Netlify’s continuous deployment, if you clicked the “Deploy to Netlify” button on the first step then your website will be deployed to once you merge your changes into the master branch of your project’s repository. Also, if you open a pull request Netlify will generate a preview. This should mirror what you see when running the site locally.&lt;/p&gt;
&lt;p&gt;The final code can be viewed on the &lt;code&gt;loremIpsumTutorial&lt;/code&gt; branch of the &lt;a href=&quot;https://github.com/M0nica/greys-anatomy-lorem-ipsum-generator/tree/loremIpsumTutorial&quot;&gt;greys-anatomy-lorem-ipsum-generator&lt;/a&gt; repository.&lt;/p&gt;
&lt;p&gt;Please share with me on Twitter, &lt;a href=&quot;https://www.twitter.com/indigitalcolor&quot;&gt;@indigitalcolor&lt;/a&gt; if you end up creating a Lorem Ipsum generator or another app with the Netlify functions after reading through this tutorial.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Hello, IndieWeb!</title><link>https://aboutmonica.com/blog/hello-indie-web/</link><guid isPermaLink="true">https://aboutmonica.com/blog/hello-indie-web/</guid><description>Some thoughts on tinkering around with my digital playground and getting more involved with the IndieWeb.</description><pubDate>Mon, 17 Mar 2025 22:22:11 GMT</pubDate><content:encoded>&lt;p&gt;For years  I’ve considered this website my digital playground but it&apos;s not just a playground, it&apos;s also a learning space. Through the process of crafting this site I&apos;ve learned more not only about technology but also about myself as a technologist (and a glimpse of how I&apos;d be as a designer 🪄). I really appreciate this metaphor from Aral Balkan about how hands-on creation is an essential part of the learning process. The act of making itself deepens your understanding of the medium, its constraints, and its possibilities.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Coding is like taking a lump of clay and slowly working it into the thing you want it to become. It is this process, and your intimacy with the medium and the materials you’re shaping, that teaches you about what you’re making – its qualities, tolerances, and limits – even as you make it. You know the least about what you’re making the moment before you actually start making it. - &lt;a href=&quot;https://mastodon.ar.al/@aral/114160190826192080&quot;&gt;Aral Balkan&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&apos;s also fun to have a place to explore and experiment  even when it doesn’t have a &lt;a href=&quot;https://lynnandtonic.com/thoughts/entries/why-do-work-without-a-practical-purpose/&quot;&gt;practical purpose&lt;/a&gt; which is what first sparked my interest in web development.&lt;/p&gt;
&lt;p&gt;Last week, I updated my website to accept &lt;a href=&quot;https://aboutmonica.com/webmentions/&quot;&gt;Webmentions&lt;/a&gt; again and was reading up on different web standards related to the &lt;a href=&quot;https://indieweb.org/&quot;&gt;IndieWeb&lt;/a&gt;. I kept discovering new ideas as the week went a long but my main takeaway is that I’m really looking forward to continue tinkering with this site and have no idea what it &lt;em&gt;might&lt;/em&gt; look like a year from now. I re-wrote my site last year in &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; which helped make making site updates enjoyable again. Before this switch my website was in a state where there were some outdated packages that my site relied on that couldn&apos;t be upgraded and my site always seemed to be at least a major version behind the &lt;a href=&quot;https://www.gatsbyjs.com/&quot;&gt;GatsbyJS&lt;/a&gt; framework.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/monica-personal-website-readme.png&quot; alt=&quot;A screenshot of a README with a small cartoon of a playground&apos;s slide at the top. The text reads Welcome to Monica&apos;s Site Playground. This is an ongoing project created and maintained by Monica Powell. The production version of this project lives at: https://www.aboutmonica.com/. I&apos;ve started to consider my site my playground, similar to a digital garden worthy of a love letter. I decided to completely re-do my website in September and am pretty happy with where it landed. Instead of managing a separate blog and portfolio site I combined the two which has given me the flexibility to experiment with different website content hierarchies. The site was written with GatsbyJS and through building my site I learned more about writing JavaScript in a server-side rendered site and how to use GraphQL in Gatsby.&quot; /&gt;
&lt;em&gt;Even though I&apos;ve since changed the underlying technologies and wrote this README for the previous version of my website, the feeling of this being my personal playground has not changed.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At the beginning of the year, I set the intention of spending more time doing tactile hobbies and activities which has led me to spend more time playing tabletop games, puzzles and journaling. I’ve also started reading more physical books this year although I&apos;m still reading e-books [^1]. This has shifted how I view the infinite scroll and I’ve deleted most-video centric apps from my phone aside from YouTube. Since I&apos;ve shifted to primarily using a desktop, I&apos;ve noticed a decline in my interest in certain websites, partly because they&apos;re not as readily accessible as apps on my phone, and also because I&apos;ve discovered a wider range of engaging content online&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/computer-room.jpg&quot; alt=&quot;3D Rendering of a vibrant pink and orange desktop setup that is slightly abstract.&quot; /&gt;
&lt;em&gt;I&apos;m not opposed to bringing back the concept of a computer room. I already treat my 15&quot; laptop like a desktop. Image source: &lt;a href=&quot;https://www.freepik.com/&quot;&gt;FreePik&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I feel a bit out of the loop now that I’m spending less time on social media. So I&apos;m still sorting out the balance between being informed but not overly distracted by endless notifications and an infinite amount of content.  I recently stumbled upon a post about the &lt;a href=&quot;https://cassidoo.co/post/human-curation/&quot;&gt;lack of human curation on the modern web&lt;/a&gt; by Cassidy Williams where she says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the earlier internet days, you went to a fun website or read the latest thing because you decided to go do it. Now, all of this content is pushed in your face&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So far, I’m enjoying spending more of my screen-time, tinkering around with my digital playground, exploring the &lt;a href=&quot;https://indieweb.org/&quot;&gt;IndieWeb&lt;/a&gt; and other personal websites[^2]. The IndieWeb is an alternative to the corporate web and defines itself as &quot;a community of independent and personal websites based on the principles of: owning your domain and using it as your primary online identity, publishing on your own site first (optionally elsewhere), and owning your content&quot;. I&apos;m drawn to the idea of having my own little piece of the internet that I can share with others and it&apos;s nice to know that there&apos;s a &quot;vast community of fellow web weavers&quot;[^3] out there.&lt;/p&gt;
&lt;p&gt;If you have any suggestions or RSS feed recommendations, I&apos;m eager to get more involved with the IndieWeb and would love to hear them! I can be reached via &lt;a href=&quot;https://aboutmonica.com/webmentions/&quot;&gt;Webmention&lt;/a&gt;,  &lt;a href=&quot;https://bsky.app/profile/monica.dev&quot;&gt;BlueSky&lt;/a&gt; or &lt;a href=&quot;https://hachyderm.io/@indigitalcolor&quot;&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;[^1]: I usually borrow e-books from &lt;a href=&quot;https://libbyapp.com/&quot;&gt;Libby&lt;/a&gt; and as of February 2025 I’m looking for a new place to purchase e-books from instead of Amazon. Libby is an app created by OverDrive that allows users to borrow books from their local libraries.&lt;/p&gt;
&lt;p&gt;[^2]: I could look at personal sites all day 😄. For years, I&apos;ve been jotting down personal sites and portfolios that inspire me. I should find a way to showcase or highlight some of them...&lt;/p&gt;
&lt;p&gt;[^3]: &quot;There is a vast community of fellow web weavers out there. And with it conversations to be started, friends to be made, visions to forge.&quot; - &lt;a href=&quot;https://jamesg.blog/2025/03/13/website-reflections&quot;&gt;Web Reflections from James&lt;/a&gt;&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Hide Your API Keys</title><link>https://aboutmonica.com/blog/hide-your-api-keys/</link><guid isPermaLink="true">https://aboutmonica.com/blog/hide-your-api-keys/</guid><description>If you plan on programming any applications and storing your code in a public GitHub repository then it is important that you protect your API keys 🔑 by ensuring that they are not searchable or…</description><pubDate>Thu, 26 Jan 2017 01:23:25 GMT</pubDate><content:encoded>&lt;h3&gt;How to Hide Your API Keys in Python 🔑&lt;/h3&gt;
&lt;h4&gt;Protect your application’s API Keys while committing to Git.&lt;/h4&gt;
&lt;p&gt;&amp;lt;figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/hide-your-api-keys-0.jpeg&quot; alt=&quot;black-and-white-code-programming-tech-79290&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;If you plan on programming any applications and storing your code in a public &lt;a href=&quot;https://github.com/&quot;&gt;GitHub&lt;/a&gt; repository then it is important that you &lt;strong&gt;protect your API keys&lt;/strong&gt; 🔑 by ensuring that they are not searchable or otherwise publicly accessible.&lt;/p&gt;
&lt;h4&gt;What’s an API?&lt;/h4&gt;
&lt;p&gt;An application programming interface (API) is a structured set of instructions for building applications. If you want to leverage data from services such as Twitter, The New York Times, &lt;a href=&quot;https://medium.com/u/26d90a99f605&quot;&gt;Slack&lt;/a&gt;, &lt;a href=&quot;https://medium.com/u/60a317bb70e4&quot;&gt;Spotify&lt;/a&gt;, etc. then you should read their APIs to figure out how to structure your queries to receive data from their service or to post on their service.&lt;/p&gt;
&lt;p&gt;Example APIs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://dev.twitter.com/docs&quot;&gt;&lt;strong&gt;Twitter Developer Documentation - Twitter Developers&lt;/strong&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.spotify.com/web-api/&quot;&gt;&lt;strong&gt;Spotify Web API - Spotify Developer&lt;/strong&gt; &lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://api.slack.com/&quot;&gt;&lt;strong&gt;Slack API&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.nytimes.com&quot;&gt;&lt;strong&gt;API Gallery - NYT Developers Network&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;What are API keys?&lt;/h4&gt;
&lt;p&gt;API keys allow developers to access APIs and are unique keys associated with that particular developer and/or application. Just like you shouldn’t share your passwords you should &lt;strong&gt;never&lt;/strong&gt; share your API keys. It is important to protect your API keys so that people do not take any actions as you which could result in your API key being revoked due to somebody else exceeding rate limits or abusing/violating an APIs terms of service. A rate limit is when an application limits the number of API calls that a specific application or user can make during a specified period of time.&lt;/p&gt;
&lt;h4&gt;How do I protect my API keys on Github?&lt;/h4&gt;
&lt;p&gt;Here’s how to hide API keys in Python from GitHub using config.py to store your sensitive API keys and tokens in a separate file from your main script. I used similar code when accessing the Twitter Search API for my &lt;a href=&quot;https://github.com/M0nica/blackgirlmagic&quot;&gt;blackgirlmagic twitter bot&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Create 3 Files in Your Application&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;config.py&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This file will store your API keys. You just need to update the portion in the strings with your API keys, depending on the service you may or may not need all four types of API keys. These in particular are required to create a Twitter application.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;api_key = &quot;YOUR_KEY&quot;
api_secret = &quot;YOUR_SECRET&quot;
access_token = &quot;YOUR_ACCESS_TOKEN&quot;
token_secret = &quot;YOUR_TOKEN_SECRET&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;main_script.py&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This file will store your main script that needs to access the API keys. This file can be named whatever you like.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import config

from twython import Twython, TwythonError

# create a Twython object by passing the necessary secret passwords
twitter = Twython(config.api_key, config.api_secret, config.access_token, config.token_secret)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;strong&gt;.gitignore&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;A .gitignore file tells GitHub to ignore the noted files, directories or files that end in specific extensions when committing files to GitHub. &lt;strong&gt;This step is crucial to ensure that your config.py file does not end up viewable on GitHub! Here’s&lt;/strong&gt; &lt;a href=&quot;https://github.com/github/gitignore&quot;&gt;&lt;strong&gt;a collection of useful .gitignore templates&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/github/gitignore&quot;&gt;&lt;strong&gt;github/gitignore&lt;/strong&gt;&lt;br /&gt;
_A collection of useful .gitignore templates_github.com&lt;/a&gt;&lt;a href=&quot;https://github.com/github/gitignore&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;config.py
__pycache__
.ipynb_checkpoints
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Feel free to reach out below with any comments or questions that you have. I would love to know how you hide your API keys when creating applications in Python or any other languages.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>How to Change Repo Language in GitHub</title><link>https://aboutmonica.com/blog/how-to-change-repo-language-in-github/</link><guid isPermaLink="true">https://aboutmonica.com/blog/how-to-change-repo-language-in-github/</guid><description>I recently started working on a Weather app in Flask to auto-detect a user’s location based off of their IP address. After committing some updates to GitHub my app switched from being labeled as…</description><pubDate>Tue, 07 Feb 2017 14:17:11 GMT</pubDate><content:encoded>&lt;h3&gt;How to Change Repo Language in GitHub&lt;/h3&gt;
&lt;h4&gt;Is GitHub telling you that your repository is 98.9% CSS or HTML when it isn’t? Here’s how to resolve that issue every time.&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/media/how-to-change-repo-language-in-github-0.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I recently started working on a &lt;a href=&quot;https://github.com/M0nica/flask_weather&quot;&gt;Weather app in Flask&lt;/a&gt; to auto-detect a user’s location based off of their IP address. After committing some updates to GitHub my app switched from being labeled as predominately Python to 98.9% CSS even though it was a Flask application in which most of the code I had written was in Python and HTML. Now and again, I do not agree with how GitHub classifies the languages in my repositories so I set out to figure out how to fix this issue.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/how-to-change-repo-language-in-github-1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Before: My Flask App Appeared in GitHub as 98.9% CSS.&lt;/p&gt;
&lt;h4&gt;Pro-tip: Help GitHub properly detect your repositories main language(s).&lt;/h4&gt;
&lt;p&gt;GitHub has a &lt;a href=&quot;https://github.com/github/linguist&quot;&gt;linguist library&lt;/a&gt; that auto-detects the language within every repository. Upon researching how to resolve GitHub misclassifying the language of your projects I found out the solution is as simple as telling GitHub which files to ignore.&lt;/p&gt;
&lt;p&gt;While you still want to commit these files to GitHub and therefore can’t use a .gitignore you can tell GitHub’s linguist which files to ignore in a .gitattribute file.&lt;/p&gt;
&lt;p&gt;Upon examining the documentation for the linguist library I learned that adding just one line to a .gitattributes file would resolve my language issues for this particular repo.&lt;/p&gt;
&lt;p&gt;My .gitattributes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static/* linguist-vendored
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This one-line file told GitHub to ignore all of my files in my static/ folder which is where CSS and other assets are stored for a Flask app. Vendor files can sometimes take up a lot of relative space so I am telling the linguist to just ignore them (since they were accounting for 98.9% of my project)!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/how-to-change-repo-language-in-github-2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After: My Flask App Appears in GitHub now as 56.2% Python and 43.8% HTML.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/alexkaratarakis/gitattributes&quot;&gt;Here’s a repository by @with sample .gitattributes files for you try the next time you disagree with the linguist ;).&lt;/a&gt; Note: Updates to the .gitattributes may not apply retroactively and if linguist &lt;em&gt;truly&lt;/em&gt; is wrong GitHub encourages you to report it as an issue in their /linguist repo.&lt;/p&gt;
&lt;p&gt;I hope this article was helpful! I would love to hear some of your tricks for GitHub and am happy to answer any questions you may have.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you enjoyed reading this article consider tapping the clap button 👏. Wanna see more of my work? Check out&lt;/em&gt; &lt;a href=&quot;https://github.com/M0nica/&quot;&gt;&lt;em&gt;my GitHub&lt;/em&gt;&lt;/a&gt; &lt;em&gt;to view my code and learn more about my development experience at&lt;/em&gt; &lt;a href=&quot;http://aboutmonica.com&quot;&gt;&lt;em&gt;http://aboutmonica.com&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>How to Check Bundled Netlify Function Size Locally</title><link>https://aboutmonica.com/blog/how-to-check-bundled-netlify-function-size-locally/</link><guid isPermaLink="true">https://aboutmonica.com/blog/how-to-check-bundled-netlify-function-size-locally/</guid><description>Is everything working locally but you&apos;re having trouble deploying a Netlify function due to it being greater than 50 MB? This article walks through how to use the Netlify CLI to determine the size of the bundled Netlify function locally.</description><pubDate>Sun, 02 Jan 2022 19:05:25 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;Does your Netlify function appear to be working locally but you&apos;re having trouble successfully deploying the function due to it being greater than 50 MB? This article walks through how to use the Netlify CLI to determine the size of the bundled Netlify function locally in order to more quickly optimize the size.&lt;/p&gt;
&lt;h2&gt;Relationship between AWS Lambda 50 MB and Netlify Functions&lt;/h2&gt;
&lt;p&gt;Under the hood Netlify functions are an abstraction build on top of AWS Lambda functions. Therefore, Netlify functions are subject to the same limits as AWS Lambda functions and currently &lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html&quot;&gt;AWS restricts zipped Lambda files to 50 MB&lt;/a&gt;. During the build process Netlify bundles any Netlify functions, by default with the zip-it-and-ship it bundler and attempts to upload the zipped version. If a Netlify function is greater than 50 MB after the bundling process then during the build process when uploading the zipped function you will receive an error similar to the below:&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;danger&quot;&amp;gt;
Request must be smaller than 69905067 bytes for the CreateFunction operation
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h2&gt;How to Determine Size of Zipped Netlify Function Locally&lt;/h2&gt;
&lt;p&gt;You can often get feedback more quickly on the size of your Lambda functions by testing locally whether or not the zipped artifacts for your Netlify functions are less than 50MB without having to do a full remote build.&lt;/p&gt;
&lt;p&gt;I was recently working on a Netlify function to programmatically generate meta images and required the following dependencies dotenv, node-fetch, chrome-aws-lambda and puppeteer. It was working as expected locally and remotely but once I added an additional dependency of Cloudinary I began to encounter issues with the function size being too large and needed to experiment with potential optimizations like, will using puppeteer directly from chrome-aws-lambda meaningfully reduce the overall size of the function? Or, will switching the bundler reduce the size of the function below 50MB? The ability to test the impact of my changes of the overall bundle size locally without having to commit them and wait for remote CI/CD was helpful for reducing the amount of time it took to get feedback on my changes and allowed me to land on a working solution more quickly.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In order to most effectively work with Netlify functions in a development environment you should use the &lt;a href=&quot;https://docs.netlify.com/cli/get-started/&quot;&gt;Netlify CLI&lt;/a&gt; as it allows you to run a local development server and interact with Netlify in a similar context to your hosted build configuration. The Netlify CLI can be installed globally with:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;npm install -g netlify-cli
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to interact with your hosted Netlify site you will need to authenticate by logging into Netlify and link the local project to a site hosted in your Netlify account:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Login to Netlify
netlify login

# Link current directory to a remote Netlify site
netlify link
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once everything is configured for the Netlify CLI you should be able to run a build locally that mirrors what happens when Netlify generates a deploy preview remotely which is a similar context to where the error related to file function size occurs.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;netlify build --context deploy-preview
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will run a full build of your site and show in the logs each build step as it occurs and whether or not there are any errors. If everything succeeded then you should be able to view the functions generated by the build in a hidden &lt;code&gt;.netlify&lt;/code&gt; directory within your site which contains your functions folder and both the zipped and unzipped versions of the functions. Since we&apos;re concerned about the size of the generated zipped version of the function that is the file that we need to examine closer.&lt;/p&gt;
&lt;p&gt;We can quickly inspect the file path of the zipped function that was generated during the build with &lt;code&gt;du&lt;/code&gt; which is a unix command to get information on disk usage and use the -h flag to render the output in a &quot;human-readable&quot; format. Note since this format is human-readable it&apos;s an approximation of the actual number.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# du -h .netlify/functions/getMetaImg.zip
du -h path/to/your/generated/netlify/function.zip

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the above &lt;code&gt;du&lt;/code&gt; command the example output for a 50MB file would be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 50M	.netlify/functions/getMetaImg.zip
50m path/to/your/generated/netlify/function.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also navigate to the file via your computer&apos;s GUI file navigator such as MacOS&apos;s Finder or Windows&apos; File Explorer and view more information for the file to view the file size. I found that looking at the file in the MacOS Finder and then right-clicking &quot;more info&quot; provided me with more significant digits for the same file than the number returned by &lt;code&gt;du&lt;/code&gt;. File Explorer can also be configured to display file sizes.&lt;/p&gt;
&lt;h2&gt;Reducing Size of Netlify functions&lt;/h2&gt;
&lt;p&gt;In order to be able to successfully upload and deploy your function you will need to figure out a way to reduce the size to be no greater than 50 MB by removing unused dependencies, swapping out
dependencies with lighter-weight alternatives or updating how your functions are being bundled for production. One of the fastest ways to reduce the overall size of Netlify functions is by using the &lt;a href=&quot;https://esbuild.github.io/&quot;&gt;esbuild&lt;/a&gt; bundler instead of the currently default for Netlify functions &lt;a href=&quot;https://github.com/netlify/zip-it-and-ship-it&quot;&gt;zip it and ship it (a.k.a zisi)&lt;/a&gt;. In April 2021, &lt;a href=&quot;https://www.netlify.com/blog/2021/04/02/modern-faster-netlify-functions/&quot;&gt;Netlify announced beta support for esbuild&lt;/a&gt; as a more modern and faster, alternative to zisi.
You can enable esbuild for your Netlify functions by updating the &lt;code&gt;netlify.toml&lt;/code&gt; in the root of your project to include the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[functions]
  node_bundler = &quot;esbuild&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I updated my &lt;code&gt;netlify.toml&lt;/code&gt; and then was able to determine that the same function bundled with &lt;code&gt;esbuild&lt;/code&gt; and &lt;code&gt;zisi&lt;/code&gt; was ~2MB smaller in the &lt;code&gt;esbuild&lt;/code&gt; version by doing a local deploy-preview build of my site with the Netlify CLI and then running &lt;code&gt;du&lt;/code&gt; to view the file size. However, if the file is still too large you will need to do a closer evaluation of the dependencies your function relies onto function to determine if there are opportunities to reduce or replace larger dependencies.&lt;/p&gt;
&lt;p&gt;Initially, I encountered some issues using esbuild out of the box which caused a &lt;code&gt;Error: spawn ETXTBSY&lt;/code&gt; error when running the deployed function as the zipped version excluded one of my dependencies &lt;code&gt;chrome-aws-lambda&lt;/code&gt;. I was able to determine this by checking the Netlify logs which output the following tip during the Functions bundling step:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;────────────────────────────────────────────────────────────────
  4. Functions bundling
 ────────────────────────────────────────────────────────────────
 ​
 Packaging Functions from netlify/functions directory:
  - getMetaImg.js
 - gatsby/gatsby.js
 ​
❯ The following Node.js modules use dynamic expressions to include files:
   - chrome-aws-lambda

  Because files included with dynamic expressions aren&apos;t bundled with your serverless functions by default,
  this may result in an error when invoking a function. To resolve this error, you can mark these Node.js modules as external in the [functions] section of your `netlify.toml` configuration file:
​
   [functions]
      external_node_modules = [&quot;chrome-aws-lambda&quot;]

    Visit https://ntl.fyi/dynamic-imports for more information.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My issues with the deployed function were resolved after I updated my esbuild configuration to include that dynamic dependency of &quot;chrome-aws-lambda&quot; by adding the following to the netlify.toml&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#replace functionName with the name of your function or use [functions] to apply to all of the Netlify # functions configured by that netlify.toml
[functions.functionName]
  external_node_modules = [&quot;chrome-aws-lambda&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are noticing differences in behavior between the esbuild and zisi version of your function you should double-check the logs to help determine if dynamic imports that you need were not removed during the esbuild process and if so, update the &lt;a href=&quot;https://docs.netlify.com/configure-builds/file-based-configuration/#functions&quot;&gt;functions configuration&lt;/a&gt; as needed.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Using the Netlify CLI can be a helpful resource for developing and testing Netlify functions locally. By creating a local deploy-preview build of your site with the Netlify CLI you can gain meaningful insight into issues that arise during remote deployments and shorten the feedback loop to debug issues that do not appear during normal local development. If you&apos;re using the default, zisi, bundler for Netlify functions you may want to consider switching over to esbuild.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>How To Create A GitHub Profile README</title><link>https://aboutmonica.com/blog/how-to-create-a-github-profile-readme/</link><guid isPermaLink="true">https://aboutmonica.com/blog/how-to-create-a-github-profile-readme/</guid><description>Tips and inspiration for enhancing your GitHub Profile</description><pubDate>Sat, 11 Jul 2020 11:25:50 GMT</pubDate><content:encoded>&lt;p&gt;import { Tweet } from &quot;@astro-community/astro-embed-twitter&quot;;&lt;/p&gt;
&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;GitHub recently released a feature that allows users to create a profile-level README to display prominently on their GitHub profile. This article walks through how to access this new feature. I&apos;ll also be sharing some fun GitHub profiles I&apos;ve seen so far. I&apos;d love it if you shared yours with me on Twitter &lt;a href=&quot;https://twitter.com/indigitalcolor&quot;&gt;@indigitalcolor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/monica-github-readme-edit.gif&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;The above GIF shows what my README looks like at the time of this writing. You may notice I was recently selected to be &lt;a href=&quot;https://stars.github.com/&quot;&gt;GitHub star&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Why READMEs?&lt;/h2&gt;
&lt;p&gt;The GitHub profile-level README feature allows more content than the profile bio, supports markdown which means you can play around with the content more visually (Did someone say GIFs!?) and the README is significantly more visible as it is placed above pinned repositories and takes up as much space above the fold of the webpage as you like.&lt;/p&gt;
&lt;p&gt;A solid README is a core-component of well-documented software and often encourages collaboration by sharing helpful context with contributors. In my opinion, a profile-level README seems like a great extension of a convention a lot of GitHub users are already familiar with. If you&apos;re looking to make project-level READMEs more awesome and helpful check out &lt;a href=&quot;https://github.com/matiassingers/awesome-readme&quot;&gt;matiassingers/awesome-readme&lt;/a&gt; for resources and examples of compelling READMEs.&lt;/p&gt;
&lt;h2&gt;How do I create a profile README?&lt;/h2&gt;
&lt;p&gt;The profile README is created by creating a new repository that’s the same name as your username. For example, my GitHub username is m0nica so I created a new repository with the name m0nica. Note: at the time of this writing, in order to access the profile README feature, the letter-casing &lt;strong&gt;must&lt;/strong&gt; match your GitHub username.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;danger&quot;&amp;gt;
&amp;lt;p&amp;gt;
&amp;lt;b&amp;gt;Already have a repo-named username/username?&amp;lt;/b&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;
If you are interested in setting up a profile-level README then you can {&quot;&quot;}
&amp;lt;a href=&quot;https://docs.github.com/en/github/administering-a-repository/renaming-a-repository&quot;&amp;gt;
rename the repository
&amp;lt;/a&amp;gt;
or repurpose its existing README based on what makes the most sense in your
particular situation.
&amp;lt;/p&amp;gt;
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new repository with the same name (including casing) as your GitHub username: https://github.com/new&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a README.md file inside the new repo with content (text, GIFs, images, emojis, etc.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Commit your fancy new README!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you&apos;re on GitHub&apos;s web interface you can choose to commit directly to the repo&apos;s main branch (i.e., &lt;code&gt;master&lt;/code&gt; or &lt;code&gt;main&lt;/code&gt;) which will make it immediately visible on your profile)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Push changes to GitHub (if you made changes locally i.e., on your computer and not github.com)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/media/create-repository.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Fun READMEs&lt;/h2&gt;
&lt;p&gt;The GitHub README profiles are written in Markdown which means you aren&apos;t just limited to texts and links, you can include GIFs and images. Need to brush up on Markdown Syntax? &lt;a href=&quot;https://guides.github.com/pdfs/markdown-cheatsheet-online.pdf&quot;&gt;Check out this Markdown Cheatsheet&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;Tweet id=&quot;https://twitter.com/jlengstorf/status/1281026687103168512&quot; /&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/donavon/status/1281231777475026945&quot; /&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/Saadeghi/status/1281111778290786310&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;If you&apos;re really ambitious you can use &lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub actions&lt;/a&gt; or other automation like bdougieYO or simonw to dynamically pull data into your README:&lt;/p&gt;
&lt;p&gt;&amp;lt;Tweet id=&quot;https://twitter.com/bdougieYO/status/1281699715466199040&quot; /&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/simonw/status/1281435464474324993&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Serverless functions can also be used to dynamically generate information (for example your current Spotify activity):&lt;/p&gt;
&lt;p&gt;&amp;lt;Tweet id=&quot;https://twitter.com/n_moore/status/1282326538990563329&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;I&apos;m a huge proponent that folks should maintain a website they have complete ownership over (even if it&apos;s a no-code website solution) but this is tempting...&lt;/p&gt;
&lt;p&gt;&amp;lt;Tweet id=&quot;https://twitter.com/TerryTangYuan/status/1281590275660537858&quot; /&amp;gt;
&amp;lt;Tweet id=&quot;https://twitter.com/pifafu/status/1265773172520914944&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;I&apos;ve been inspired by the creative READMEs I&apos;ve seen so far and am looking forward to seeing all kinds of profiles in the upcoming months.&lt;/p&gt;
&lt;p&gt;{/* we need javascript here to load the twitter embedded media properly, javascript is not required for text-only tweets */}&lt;/p&gt;
&lt;p&gt;&amp;lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>How to Make Your VSCode Sparkle</title><link>https://aboutmonica.com/blog/how-to-make-your-vs-code-sparkle/</link><guid isPermaLink="true">https://aboutmonica.com/blog/how-to-make-your-vs-code-sparkle/</guid><description>This article shows you how to customize your VSCode set up to display animated sparkles while you are typing. There&apos;s a VSCode extension, Power Mode that adds animated flair while you&apos;re typing. It has a few different default animations but none of them quite sparkle.</description><pubDate>Fri, 04 Sep 2020 18:14:40 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;This article shows you how to customize your VSCode set up to display animated sparkles while you are typing. There&apos;s a VSCode extension, &amp;lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=hoovercj.vscode-power-mode&quot;&amp;gt;Power Mode&amp;lt;/a&amp;gt; that adds animated flair while you&apos;re typing. It has a few different default animations but none of them quite sparkle. If you&apos;re not using VSCode, check out these &amp;lt;a href=&quot;https://github.com/codeinthedark/awesome-power-mode&quot;&amp;gt;Power Mode extensions&amp;lt;/a&amp;gt; for various code editors. The implementation of each differ but some of them support customized animations.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/vscode-sparkles-demo.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Above is a GIF of animated sparkles in my VSCode. The editor&apos;s theme is Night Owl and the font is Dank Mono. You can learn more about my VSCode setup on my &lt;a href=&quot;/uses&quot;&gt;uses&lt;/a&gt; page.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Enable Power Mode Extension for VS Code Typing Animation&lt;/h2&gt;
&lt;p&gt;&amp;lt;Callout&amp;gt;
&amp;lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=hoovercj.vscode-power-mode&quot;&amp;gt;
Install
&amp;lt;/a&amp;gt;
the VSCode Power Mode extension
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;In order to add animations to your VSCode first you need to install Power Mode and enable it by updating your &lt;a href=&quot;https://code.visualstudio.com/docs/getstarted/settings#_settings-file-locations&quot;&gt;VSCode settings&lt;/a&gt; to include the following:
&lt;code&gt;&quot;powermode.enabled&quot;: true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Depending on your operation system the VSCode settings file is located here:&lt;/p&gt;
&lt;p&gt;&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;bold&amp;gt;Windows&amp;lt;/bold&amp;gt; - %APPDATA%\Code\User\settings.json
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;bold&amp;gt;macOS&amp;lt;/bold&amp;gt; - $HOME/Library/Application
Support/Code/User/settings.json
&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;bold&amp;gt;Linux&amp;lt;/bold&amp;gt; - $HOME/.config/Code/User/settings.json
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;&lt;/p&gt;
&lt;h2&gt;Customize How Power Mode Renders Animations&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;&quot;powermode.enabled&quot;&lt;/code&gt; turns on the extension&apos;s default animations when you&apos;re typing but if you want sparkles then some additional customization is required.
In particular, you need to configure &lt;code&gt;&quot;powermode.customExplosions&quot;&lt;/code&gt; in settings and pass in your own animation to be used for the explosions. This setting supports base64 encoded gifs, absolute file path, or https image URLs.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; &quot;powermode.customExplosions&quot;: [
// absolute path to custom ✨ GIF on my computer
    &quot;/Users/monica/Dev/power-mode-sparkles/sparkles.gif&quot;
  ],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are interested in other animation ideas aside from sparkles check out the &lt;a href=&quot;https://github.com/hoovercj/vscode-power-mode/issues/1&quot;&gt;vscode-power-mode/Help wanted: explosion gifs&lt;/a&gt; issue on GitHub. In fact, this issue is where I found the image for my sparkles ✨ thanks to &lt;a href=&quot;https://github.com/hoovercj/vscode-power-mode/issues/1#issuecomment-643829082&quot;&gt;federicca&lt;/a&gt;. You can &lt;a href=&quot;#sparkle-gif-to-download&quot;&gt;Download&lt;/a&gt; the Sparkle GIF I used for my configuration.&lt;/p&gt;
&lt;h2&gt;My Final Power Mode VSCode Configuration&lt;/h2&gt;
&lt;p&gt;My VSCode &lt;code&gt;settings.json&lt;/code&gt; includes the following keys and values after configuring Power Mode:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// turn on powermode 🚨
&quot;powermode.enabled&quot;: true,
  &quot;powermode.customExplosions&quot;: [
// absolute path to custom ✨ GIF on my computer
    &quot;/Users/monica/Dev/power-mode-sparkles/sparkles.gif&quot;
  ],
  // enableShake is true by default which causes the
  // text/screen to shake, the following line disables that
   &quot;powermode.enableShake&quot;: false,
  // tweaking settings to reduce amount of explosions
  &quot;powermode.maxExplosions&quot;: 1,
  &quot;powermode.explosionFrequency&quot;: 4,
  //backgroundMode mask - animation matches the current syntax color
  //backgroundMode image - displays animation with original colors from the image
  &quot;powermode.backgroundMode&quot;: &quot;mask&quot;,
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Optimizing Power Mode&lt;/h3&gt;
&lt;p&gt;Power Mode recommends tweaking some of the default settings to reduce the frequency of animations if the extension is causing your editor to slow down.&lt;/p&gt;
&lt;h2&gt;Sparkle GIF to Download&lt;/h2&gt;
&lt;p&gt;Right-click and save this image to your computer if you&apos;re ready to start adding more sparkles to your development workflow.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/sparkles.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>How To Render Relevant Icons Based on Content</title><link>https://aboutmonica.com/blog/how-to-render-relevant-icons-based-on-content/</link><guid isPermaLink="true">https://aboutmonica.com/blog/how-to-render-relevant-icons-based-on-content/</guid><description>This article walks through how I use React to render icons that are tailored to the specific content.</description><pubDate>Sat, 12 Sep 2020 13:40:05 GMT</pubDate><content:encoded>&lt;p&gt;This article walks through how I display relevant, themed icons when linking to content on this site based on the topic or type of content.
If you&apos;ve visited the homepage of this site, I use a variation of this component to display my recent content, featured content, and recent video lessons.&lt;/p&gt;
&lt;h2&gt;ArticleList: Rendering Article Titles + Icons&lt;/h2&gt;
&lt;p&gt;The logic for displaying article titles alongside a relevant icon is constructed within an &lt;code&gt;&amp;lt;ArticleList/&amp;gt;&lt;/code&gt; component. Below is an example of a fully functioning, interactive &lt;code&gt;&amp;lt;ArticleList/&amp;gt;&lt;/code&gt; component (thanks to &lt;a href=&quot;/blog/thoughts-on-migrating-from-markdown-to-mdx/&quot;&gt;MDX&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;import ArticleList from &quot;../../components/ArticleList.jsx&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;ArticleList
articles={[
{
slug: &quot;create-gatsby-blog-search-tutorial/&quot;,
tags: [&quot;gatsby&quot;],
title: &quot;How to Add Search Functionality to a Gatsby Blog&quot;,
},
{
slug: &quot;2020-01-29-automating-file-creation-with-javascript/&quot;,
tags: [&quot;javascript&quot;],
title: &quot;Automating File Creation With JavaScript&quot;,
},
{
slug: &quot;how-to-create-a-github-profile-readme/&quot;,
tags: [&quot;Git/GitHub&quot;],
title: &quot;How To Create A GitHub Profile README&quot;,
},
]}
/&amp;gt;&lt;/p&gt;
&lt;p&gt;In my actual site the article data is programmatically passed in from the result of a GraphQL query, however, a similar component can be used regardless of how your website handles data. I used the below JSX markup with hard-coded data to render the above instance of the &lt;code&gt;&amp;lt;ArticleList/&amp;gt;&lt;/code&gt; component:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;ArticleList
  articles={[
    {
      slug: &quot;/blog/create-gatsby-blog-search-tutorial/&quot;,
      tags: [&quot;gatsby&quot;],
      title: &quot;How to Add Search Functionality to a Gatsby Blog&quot;,
    },
    {
      slug: &quot;/blog/2020-01-29-automating-file-creation-with-javascript/&quot;,
      tags: [&quot;javascript&quot;],
      title: &quot;Automating File Creation With JavaScript&quot;,
    },
    {
      slug: &quot;/blog/how-to-create-a-github-profile-readme/&quot;,
      tags: [&quot;Git/GitHub&quot;],
      title: &quot;How To Create A GitHub Profile README&quot;,
    },
  ]}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Essentially the &lt;code&gt;&amp;lt;ArticleList/&amp;gt;&lt;/code&gt; component maps through each article in the articles array and displays the article title along with a link to the article and an article icon based on the article tags. A simplified version of the render of &lt;code&gt;&amp;lt;ArticleList/&amp;gt;&lt;/code&gt; resembles the below code. Note: Whenever mapping through items to render items in React you should use a unique &lt;code&gt;key&lt;/code&gt; in order for React to be able to efficiently differentiate between different items within the generated list:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  articles.map(({ slug, tags, title }, index) =&amp;gt; (
    &amp;lt;div key={`${index}-${slug}`}&amp;gt;
      &amp;lt;ArticleIcon tags={tags} /&amp;gt;
      &amp;lt;Link to={slug} className=&quot;article-link&quot;&amp;gt;
        {title}
      &amp;lt;/Link&amp;gt;
    &amp;lt;/div&amp;gt;
  ));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ArticleIcon: Returning the Relevant Icon&lt;/h2&gt;
&lt;p&gt;The logic to determine which icon should render for a given article based on its tags is handed off from the &lt;code&gt;&amp;lt;ArticleList/&amp;gt;&lt;/code&gt; component to the &lt;code&gt;&amp;lt;ArticleIcon/&amp;gt;&lt;/code&gt; component. Let&apos;s take a closer look at the &lt;code&gt;&amp;lt;ArticleIcon/&amp;gt;&lt;/code&gt; component.&lt;/p&gt;
&lt;h3&gt;Setting up Icons&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;ArticleIcon/&amp;gt;&lt;/code&gt; component imports multiple SVG image files for each of the potential icons that will be rendered:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import GitHubIcon from &quot;./icons/github.svg&quot;;
import ReactIcon from &quot;./icons/react.svg&quot;;
import JavascriptIcon from &quot;./icons/javascript.svg&quot;;
import PenIcon from &quot;./icons/pen.svg&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then within the &lt;code&gt;ArticleIcon&lt;/code&gt; component there&apos;s an object that contains the imported icons with key value pairs that look like [tag]: icon. I have intentionally omitted the &lt;code&gt;PenIcon&lt;/code&gt; from this object as it is the default icon that is displayed when there is not a better matching icon available for a given set of tags. I also decided to structure the object this way so that icons could quickly be retrieved based on their tag name which is the name of the properties in the icons object. In the below instance I have two separate keys for Git and GitHub since either tag should be associated with the GitHubIcon.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default function ArticleIcon({ tags }) {
  const icons = {
    git: GitHubIcon,
    github: GitHubIcon,
    react: ReactIcon,
    javascript: JavascriptIcon,
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Finding the Most Relevant Icon&lt;/h3&gt;
&lt;p&gt;After initializing the icons object the method &lt;code&gt;.find()&lt;/code&gt; is used to locate the first instance of a tag within the array of the tags (passed in via props) that matches a tag within the icons object. Since &lt;code&gt;.find()&lt;/code&gt; returns the first instance that is found if an array of tags contains multiple tags that have icons in the icons object, only the first tag that matches an icon in the icons object will be returned:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const articleTagWithIcon = (tags || []).find((tag) =&amp;gt; icons[tag.toLowerCase()]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If there is an icon associated with any of the article tags then the &lt;code&gt;articleIcon&lt;/code&gt; is set to the value of &lt;code&gt;icons[articleTagWithIcon.toLowerCase()]&lt;/code&gt; or else the icon will be set to the default icon which is the PenIcon.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const articleIcon = articleTagWithIcon
  ? icons[articleTagWithIcon.toLowerCase()]
  : PenIcon;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally we can return an image in the render of the ArticleIcon:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;return (
  &amp;lt;img
    src={articleIcon}
    alt={articleTagWithIcon ? `${articleTagWithIcon} icon` : `pen icon`}
  /&amp;gt;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;This article went over how to create the logic for a component that renders different images based on the data that is passed in. I used CSS flex for the majority of the styling of this component, if you are interested in learning more about the styling behind the &lt;code&gt;&amp;lt;ArticleList/&amp;gt;&lt;/code&gt; inspect the &lt;code&gt;&amp;lt;ArticleList/&amp;gt;&lt;/code&gt; on my website! For sake of clarity, I may have omitted some markup or classes that were used to style the component from this article.&lt;/p&gt;
&lt;h2&gt;Final Code&lt;/h2&gt;
&lt;p&gt;All together the complete &lt;code&gt;ArticleIcon.jsx&lt;/code&gt; file looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import GitHubIcon from &quot;./icons/github.svg&quot;;
import ReactIcon from &quot;./icons/react.svg&quot;;
import JavascriptIcon from &quot;./icons/javascript.svg&quot;;
import PenIcon from &quot;./icons/pen.svg&quot;;

export default function ArticleIcon({ tags }) {
  const icons = {
    git: GitHubIcon,
    github: GitHubIcon,
    react: ReactIcon,
    javascript: JavascriptIcon,
  };

  const articleTagWithIcon = (tags || []).find(
    (tag) =&amp;gt; icons[tag.toLowerCase()],
  );

  const articleIcon = articleTagWithIcon
    ? icons[articleTagWithIcon.toLowerCase()]
    : PenIcon;

  return (
    &amp;lt;span className=&quot;article-icon&quot;&amp;gt;
      &amp;lt;img
        src={articleIcon}
        alt={articleTagWithIcon ? `${articleTagWithIcon} icon` : `pen icon`}
      /&amp;gt;
    &amp;lt;/span&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Hanselminutes Podcast: Leaning Into Learning In Public</title><link>https://aboutmonica.com/blog/leaning-into-learning-in-public-with-monica-powell/</link><guid isPermaLink="true">https://aboutmonica.com/blog/leaning-into-learning-in-public-with-monica-powell/</guid><description>Monica Powell on the Hanselminutes podcast</description><pubDate>Thu, 10 Sep 2020 00:16:13 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;iframe
style=&quot;border-radius:12px&quot;
src=&quot;https://open.spotify.com/embed/episode/4fcFDUk116Xj2qMXQZPsc8?utm_source=generator&amp;amp;theme=0&quot;
width=&quot;100%&quot;
height=&quot;152&quot;
frameBorder=&quot;0&quot;
allowfullscreen=&quot;&quot;
allow=&quot;autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture&quot;
loading=&quot;lazy&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I recently had the opportunity to speak with Scott Hanselman on &lt;a href=&quot;https://hanselminutes.com/753/leaning-into-learning-in-public-with-monica-powell&quot;&gt;Hanselminutes&lt;/a&gt; about my recent &lt;a href=&quot;/blog/how-to-create-a-github-profile-readme/&quot;&gt;GitHub article&lt;/a&gt;, &lt;a href=&quot;https://joelhooks.com/digital-garden&quot;&gt;digital gardening 🌱&lt;/a&gt;, the importance of owning your own content, leaning into learning in public and more. Listen to the episode above.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Less JavaScript Makes Font Awesome More Awesome</title><link>https://aboutmonica.com/blog/less-javascript-is-more/</link><guid isPermaLink="true">https://aboutmonica.com/blog/less-javascript-is-more/</guid><description>Recently, I looked into tackling a rendering issue with Font Awesome fonts used on this site by removing JavaScript...</description><pubDate>Sat, 09 Nov 2019 02:43:13 GMT</pubDate><content:encoded>&lt;p&gt;I decided to use &lt;a href=&quot;https://fontawesome.com/&quot;&gt;Font Awesome&lt;/a&gt;&apos;s SVG icons to display social media icons prominently on this site&apos;s homepage. I am a long time fan of Font Awesome and supported their &lt;a href=&quot;https://www.Kickstarter.com/projects/232193852/font-awesome-5&quot;&gt;Kickstarter campaign&lt;/a&gt; in 2017, which was the highest-funded software project on Kickstarter at that time with a total of over 1,076,960 USD pledged. Despite its awesomeness, I &lt;em&gt;did&lt;/em&gt; run into a small issue with how the default styles were loaded when adding Font Awesome to this site.&lt;/p&gt;
&lt;h2&gt;Problem 🐛: CSS loading differently locally vs in-production&lt;/h2&gt;
&lt;p&gt;Overall, I had a smooth experience implementing Font Awesome and did not experience any major issues when developing but as soon as my site was deployed I noticed that there was a huge discrepancy in the size of the icons as they rendered.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/mutable_monica.gif&quot; alt=&quot;comparing the icon size on final load vs load without javascript&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To illustrate this problem using the above GIF, the beginning of the GIF shows how misformatted and large the social icons appeared in production initially (while waiting for JavaScript to fully load or when JavaScript was disabled). Towards the end of the GIF the icons change to the proper styled size. In development the icons &lt;em&gt;only&lt;/em&gt; appeared in the proper styled size.&lt;/p&gt;
&lt;p&gt;The below screenshot from the &lt;a href=&quot;https://www.webpagetest.org/&quot;&gt;Web Page Test&lt;/a&gt; further illustrates how the rendering issue manifested in production by showing how the icon appearances changed over time (1.5s to 1.8s) as the page loaded.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/font-awesome-rendering-issue.png&quot; alt=&quot;a screenshot from the filmstrip from web page speed test showing the icons appearing as different sizes in different snapshots over time&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Investigating why the icon styling behaved differently in production vs development led me to understand a bit more how the styles were being applied to my icons and to the realization that using Font Awesome&apos;s NPM packages out of the box in a server-side rendered (SSR) application you will likely experience some jarring rendering issues. By default, Font Awesome&apos;s NPM packages import styles from &lt;code&gt;&quot;@fortawesome/fontawesome-svg-core&quot;&lt;/code&gt;. While the CSS loaded from &lt;code&gt;&quot;@fortawesome/...&lt;/code&gt; was readily available in development this same CSS was consistently not available on the initial load of the production version of my site causing the icons to initially render too large and then to snap to their proper size. As I looked into this I discovered this rendering issue is a known Font Awesome issue that requires a little bit of additional configuration. The Font Awesome site has some &lt;a href=&quot;https://fontawesome.com/how-to-use/on-the-web/other-topics/server-side-rendering#css&quot;&gt;suggestions&lt;/a&gt; for handling the issue of CSS not being available on load for server-side rendered applications.&lt;/p&gt;
&lt;p&gt;TLDR; CSS local to your application is your friend as the underlying issue is that the availability on the initial page load of JavaScript assets is different locally vs in production for a server-side rendered application.&lt;/p&gt;
&lt;h2&gt;What is Server-side rendering?&lt;/h2&gt;
&lt;p&gt;Server-side rendering is when an application generates and sends a complete HTML page from the server to the client on the initial load that doesn&apos;t require JavaScript to be available before content can be displayed. After the initial load, Client-side rendering can take over to allow better interactivity.&quot;Without Server Side Rendering, all your server ships is an HTML page with no body, just some script tags that are then used by the browser to render the application.&quot; (source: &lt;a href=&quot;https://flaviocopes.com/react-server-side-rendering/&quot;&gt;Flavioscopes&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;For this site I am using &lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;GatsbyJS&lt;/a&gt; which has server-side rendering configured for the production build of the website which means the initial render has HTML/CSS loaded and &quot;[o]nce a site has been built by Gatsby and loaded in a web browser, client-side JavaScript assets will download and turn the site into a full React application that can manipulate the DOM.
&quot; (source: &lt;a href=&quot;https://www.gatsbyjs.org/docs/glossary#hydration&quot;&gt;Gatsby Glossary&lt;/a&gt;). In other words, the HTML/CSS for this site loads but then additional network requests are made to retrieve additional JavaScript files required for full functionality.&lt;/p&gt;
&lt;h2&gt;Solution 💡&lt;/h2&gt;
&lt;p&gt;In order to resolve the issue with the icon font size being too large on initial render, I first needed to replicate the issue in development. To replicate in development, I manually blocked JavaScript from loading for my site in the Chrome Dev Tools. For the production version I was able to block JavaScript from loading by opening the Dev Tools and going to settings &amp;gt; preferences &amp;gt; debugger &amp;gt; and then selecting &quot;disable javascript&quot; however, in development I had to block specific JS files from loading in the Network tab instead or else I saw a message that said &quot;This app works best with JavaScript enabled&quot;.&lt;/p&gt;
&lt;p&gt;You can also generate the production version of the site locally using &lt;code&gt;gatsby build&lt;/code&gt; and going to the generated files. Whis will then allow you to disable JavaScript and see how the page appears in production on initial load before JavaScript is downloaded and able to progressively enhance the page.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a production build. If the application is already running locally, stop (Ctrl + C) the development server and run &lt;code&gt;gatsby build&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The production build is output to public directory. run &lt;code&gt;gatsby serve&lt;/code&gt; to view the production site locally at http://localhost:9000.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Reproduce issue by disabling and blocking external JavaScript&lt;/h3&gt;
&lt;p&gt;Disabling JavaScript allowed me to see the larger icons and to use CSS that replicated the final styling I wanted without relying on external JavaScript.&lt;/p&gt;
&lt;p&gt;After finalizing the styles, I also needed to add the following lines to my &lt;code&gt;layout.jsx&lt;/code&gt; file. The &lt;code&gt;layout.jsx&lt;/code&gt; file is responsible for rendering the overall layout for all content on this site and is where styles are loaded.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { config, dom } from &quot;@fortawesome/fontawesome-svg-core&quot;;
config.autoAddCss = false;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above code removed the Font Awesome CSS which is loaded via JavaScript from the site. The CSS I had written with JavaScript disabled now reflected 100% how I wanted the icons to look and didn&apos;t rely on Font Awesome for additional styling. Since the CSS no longer had to be fetched from an external JavaScript file, it became available on the initial load in production and did not require the page to hydrate with additional JavaScript.&lt;/p&gt;
&lt;h3&gt;Verify Solution&lt;/h3&gt;
&lt;p&gt;Below is a screenshot from Web Page Test after updating the code to not import CSS from &lt;code&gt;fontawesome-svg-core&lt;/code&gt; and only rely on my own CSS.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/after-fixing-fontawesome-issue.png&quot; alt=&quot;a screenshot from the filmstrip from web page speed test showing the icons as the same size in each snapshot&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The Web Page Test helped me confirm that the rendering issue was resolved in addition to being able to block JavaScript in Chrome DevTools. Web Page Test and Chrome DevTools can be invaluable when debugging 🐛 something related to loading JavaScript in a SSR application.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Migrating To MDX Talk</title><link>https://aboutmonica.com/blog/migrating-to-mdx/</link><guid isPermaLink="true">https://aboutmonica.com/blog/migrating-to-mdx/</guid><description>Here&apos;s a talk I gave at MDX Conf, sharing more about my migration from Remark to MDX, how you can migrate your site and what problems you might run into. MDX allows you to write Markdown that includes React components and enables a lot more flexibility than Markdown traditionally does.</description><pubDate>Mon, 24 Aug 2020 22:22:11 GMT</pubDate><content:encoded>&lt;p&gt;Below is a link to a video of a talk I gave at MDX Conf, sharing more about my migration from Remark to MDX, how you can migrate your site and what problems you might run into. MDX allows you to write Markdown that includes React components and enables a lot more flexibility than Markdown traditionally does. You can &lt;a href=&quot;/blog/thoughts-on-migrating-from-markdown-to-mdx/&quot;&gt;check out my initial thoughts on migrating to MDX here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;{/* todo fix egghead embed &amp;lt;EggheadLesson lessonId=&quot;mdx-migrating-to-mdx&quot; /&amp;gt; */}&lt;/p&gt;
&lt;p&gt;View video of my Migrating to MDX talk which is hosted on &amp;lt;a href=&quot;https://egghead.io/lessons/mdx-migrating-to-mdx?af=9b5hrz&quot;&amp;gt;Egghead.io&amp;lt;/a&amp;gt; and the slides are available &amp;lt;a href=&quot;/migratingtomdx&quot;&amp;gt;here&amp;lt;/a&amp;gt;.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>My SVG Creation Process</title><link>https://aboutmonica.com/blog/my-svg-creation-process/</link><guid isPermaLink="true">https://aboutmonica.com/blog/my-svg-creation-process/</guid><description>An overview of my creative process for creating SVG files for CSS animation</description><pubDate>Tue, 13 Apr 2021 04:58:13 GMT</pubDate><content:encoded>&lt;p&gt;import VanillaCustomSandpack from &quot;../../components/VanillaCustomSandpack.tsx&quot;;&lt;/p&gt;
&lt;p&gt;export const seaBearDemoProps = {
html: &lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt; &amp;lt;html lang=&quot;en&quot;&amp;gt;   &amp;lt;head&amp;gt;     &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;     &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&amp;gt;     &amp;lt;title&amp;gt;Sea Bear SVG Animation&amp;lt;/title&amp;gt;     &amp;lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;   &amp;lt;/head&amp;gt;   &amp;lt;body&amp;gt;     &amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 1728 1296&quot;&amp;gt;   &amp;lt;defs /&amp;gt;   &amp;lt;g id=&quot;sea-otter&quot;&amp;gt;     &amp;lt;path id=&quot;water&quot; opacity=&quot;0&quot; fill=&quot;#74C2C2&quot; d=&quot;M-3.66 51.56h1728v1286.77h-1728z&quot; /&amp;gt;     &amp;lt;g id=&quot;paws&quot;&amp;gt;       &amp;lt;g id=&quot;left-top-lines&quot;&amp;gt;         &amp;lt;path class=&quot;top-ripple&quot; id=&quot;_x32_&quot; fill=&quot;none&quot; stroke=&quot;#FFF&quot; stroke-miterlimit=&quot;10&quot; stroke-width=&quot;4&quot; d=&quot;M493.78 659.03c-9.29-10.6-6.3-29.4 5.81-36.6&quot; /&amp;gt;         &amp;lt;path class=&quot;top-ripple&quot; fill=&quot;none&quot; stroke=&quot;#FFF&quot; stroke-miterlimit=&quot;10&quot; stroke-width=&quot;4&quot; d=&quot;M473.71 691.21c-14-16.13-18.42-40.01-11.11-60.07 4.55-12.5 14.43-24.02 27.52-26.34&quot; /&amp;gt;         &amp;lt;path id=&quot;left-paw&quot; fill=&quot;#144F4F&quot; d=&quot;M596.38 654.94c-4.11-9.29-8.51-18.95-16.45-25.3-9.17-7.33-21.81-9.03-33.45-7.48-4.03.54-8.11 1.46-11.57 3.6-2.95 1.83-5.32 4.46-7.51 7.16-6.16 7.61-11.25 16.08-16.71 26.47 29.98.36 59.98-.75 89.85-3.32-1.08.3-2.31-.69-4.16-1.13z&quot; /&amp;gt;       &amp;lt;/g&amp;gt;       &amp;lt;g id=&quot;right-top-lines&quot;&amp;gt;         &amp;lt;path id=&quot;_x32__1_&quot; fill=&quot;none&quot; stroke=&quot;#FFF&quot; class=&quot;top-ripple&quot; stroke-miterlimit=&quot;10&quot; stroke-width=&quot;4&quot; d=&quot;M1108.07 585.64c8.01 2.9 14.15 10.44 15.38 18.87&quot; /&amp;gt;         &amp;lt;path id=&quot;_x31__1_&quot; fill=&quot;none&quot; stroke=&quot;#FFF&quot; class=&quot;top-ripple&quot; stroke-miterlimit=&quot;10&quot; stroke-width=&quot;4&quot; d=&quot;M1137.59 631.1c7.93-15.89 5.01-36.54-7.02-49.61&quot; /&amp;gt;         &amp;lt;path id=&quot;right-paw&quot; fill=&quot;#144F4F&quot; d=&quot;M1115.42 656.84c2.65-16.51-7.01-33.51-21.27-42.25-14.26-8.73-32.19-10.01-48.52-6.42-4.99 1.1-9.91 2.63-14.49 4.9-20.04 9.95-30.69 32.49-37.87 56.46 17.91-1.35 35.82-2.69 53.73-4.04 22.49-1.69 45.05-3.39 68.42-8.65z&quot; /&amp;gt;       &amp;lt;/g&amp;gt;     &amp;lt;/g&amp;gt;     &amp;lt;g id=&quot;feet&quot;&amp;gt;       &amp;lt;g id=&quot;left-foot-group&quot;&amp;gt;         &amp;lt;path d=&quot;M704.12 888.33c-20.92 28.4-36.66 60.21-52.28 91.83-10.69 21.63-21.68 45.37-16.67 68.98 1.07 5.04 3.06 10.21 7.15 13.35 2.43 1.86 5.4 2.86 8.37 3.58 20.54 5 42.66-2.14 59.55-14.84s29.29-30.45 39.99-48.68c18.78-32 33.42-67.07 37.32-103.97.59-5.6.61-12.1-3.6-15.84-5.21-4.64-13.29-2.21-19.93-.07-19.6 6.34-40.72 7.92-61.05 4.58l4.46-5.93-6.46 1.05c1.5 1.86 3 3.73 3.15 5.96z&quot; opacity=&quot;.161&quot; /&amp;gt;         &amp;lt;path fill=&quot;#144F4F&quot; d=&quot;M704.21 900.02c-8.3-31.99-30.55-59.13-37.94-91.34-2.09-9.09-2.9-18.85.39-27.58 3.28-8.73 11.49-16.1 20.82-16.05 13.23.08 21.9 13.54 27.31 25.62-1.65-4.25-3.3-8.69-2.93-13.24.38-4.54 3.42-9.21 7.93-9.88 6.81-1.02 11.34 6.67 13.84 13.09-3.23-4.18-.64-11 4.22-13.07 4.86-2.07 10.74-.12 14.5 3.59s5.76 8.84 7.11 13.95c-.92-5.23-1.11-12 3.68-14.3 3.81-1.82 8.31.66 11.25 3.69 10.34 10.65 11.2 27.04 11.46 41.88l1.17 68.16c-8.04.04-15.37 4.22-22.78 7.33-17.06 7.15-36.36 8.82-54.4 4.73-3.66-.83-7.37-1.94-5.63 3.42z&quot; /&amp;gt;         &amp;lt;g id=&quot;left-bottom-lines&quot; fill=&quot;none&quot; class=&quot;bottom-ripple&quot; stroke=&quot;#FFF&quot; stroke-miterlimit=&quot;10&quot; stroke-width=&quot;4&quot;&amp;gt;           &amp;lt;path id=&quot;_x32__3_&quot; d=&quot;M633.55 734.22c-11.01 24.56-4.42 53.44 6.31 78.13 6.65 15.31 15.02 30.21 27.24 41.57&quot; /&amp;gt;           &amp;lt;path id=&quot;_x31__3_&quot; d=&quot;M643.69 870.01c-21.8-16.85-37.31-41.63-42.95-68.59&quot; /&amp;gt;         &amp;lt;/g&amp;gt;         &amp;lt;g id=&quot;left-foot-padding&quot; opacity=&quot;.8&quot;&amp;gt;           &amp;lt;ellipse transform=&quot;matrix(0.9936 -0.1129 0.1129 0.9936 -84.0448 91.9986)&quot; opacity=&quot;0.9&quot; fill=&quot;#E8C089&quot; cx=&quot;770.42&quot; cy=&quot;788.2&quot; rx=&quot;7.84&quot; ry=&quot;6.94&quot; /&amp;gt;           &amp;lt;ellipse transform=&quot;matrix(0.9936 -0.1129 0.1129 0.9936 -83.5524 89.3189)&quot; opacity=&quot;0.9&quot; fill=&quot;#E8C089&quot; cx=&quot;747&quot; cy=&quot;782.51&quot; rx=&quot;8.72&quot; ry=&quot;7.72&quot; /&amp;gt;           &amp;lt;ellipse transform=&quot;matrix(0.9936 -0.1129 0.1129 0.9936 -83.5991 86.6277)&quot; opacity=&quot;0.9&quot; fill=&quot;#E8C089&quot; cx=&quot;723.21&quot; cy=&quot;781.58&quot; rx=&quot;9.12&quot; ry=&quot;8.08&quot; /&amp;gt;           &amp;lt;ellipse transform=&quot;matrix(0.9936 -0.1129 0.1129 0.9936 -84.6096 82.7489)&quot; opacity=&quot;0.9&quot; fill=&quot;#E8C089&quot; cx=&quot;688.45&quot; cy=&quot;788.57&quot; rx=&quot;17.82&quot; ry=&quot;15.78&quot; /&amp;gt;           &amp;lt;path opacity=&quot;0.9&quot; fill=&quot;#E8C089&quot; d=&quot;M689.89,820.42c1.49,6.85,19.88,64.68,33.32,71.55c7.69,3.93,16.5-1.18,23.79-2.85c20.97-4.8,19.93-46.37,20.76-63.29c0.33-6.72,2.85-16.84-2.34-19.39c-6.27-3.08-26.44-8.4-35.3-6.67C713.94,802.91,687.56,809.66,689.89,820.42z&quot; /&amp;gt;         &amp;lt;/g&amp;gt;       &amp;lt;/g&amp;gt;       &amp;lt;g id=&quot;right-foot-group&quot;&amp;gt;         &amp;lt;path d=&quot;M1072.93 821.3c-1.61 37.3 17.53 72.62 42.26 87 2.9 1.69 6.05 3.32 7.87 6.9 1.45 2.84 1.8 6.47 1.92 9.95.6 17.16-3.46 33.99-8.62 49.59-11.95 36.15-30.18 68.23-52.69 92.72-1.89 2.06-3.86 4.09-6.14 5.12-5.8 2.6-12.2-2.14-15.9-8.87-8.07-14.67-5.99-35.58-1.95-53.18s9.74-35.56 7.68-53.81c-1.1-9.7-4.34-18.66-7.81-27.2-4.04-9.93-8.49-19.6-13.85-28.23-6.47-10.41-14.69-20.51-15.58-34.23-.88-13.46 6.08-26 14.65-32.31 8.57-6.32 18.5-7.72 28.14-9 6.67-.9 13.33-1.78 20.02-4.45z&quot; opacity=&quot;.141&quot; /&amp;gt;         &amp;lt;path fill=&quot;#144F4F&quot; d=&quot;M1008.89 859.42a258.94 258.94 0 01-9.68-74.22c.08-5.32.92-11.62 5.64-14.06 4.67-2.42 10.67.77 13.17 5.39 2.5 4.63 2.45 10.16 2.33 15.41-3.37-5.99-.45-14.94 6.15-16.86 6.6-1.93 14.19 5.38 11.73 11.8-.74-6.39 2.9-13.44 9.06-15.29s13.76 3.26 13.25 9.68c-.21-8.69 6.01-17.12 14.37-19.5 8.36-2.38 18.08 1.52 22.48 9.02 6.54 11.15 1 25.08-2.81 37.43-4.86 15.75-7.51 33.75-20.26 44.18&quot; /&amp;gt;         &amp;lt;g id=&quot;right-bottom-lines&quot; fill=&quot;none&quot; stroke=&quot;#FFF&quot; stroke-miterlimit=&quot;10&quot; stroke-width=&quot;4&quot; class=&quot;bottom-ripple&quot;&amp;gt;           &amp;lt;path id=&quot;_x32__2_&quot; d=&quot;M1107.68 828.78c18.24-10.8 25.02-34.07 25.4-55.26.28-15.68-3.36-33.47-16.72-41.68&quot; /&amp;gt;           &amp;lt;path id=&quot;_x31__2_&quot; d=&quot;M1135.85 720.72c16.5 19.23 22 47.29 13.99 71.33&quot; /&amp;gt;         &amp;lt;/g&amp;gt;         &amp;lt;g id=&quot;right-foot-padding&quot; opacity=&quot;.8&quot;&amp;gt;           &amp;lt;circle opacity=&quot;0.9&quot; fill=&quot;#E8C089&quot; cx=&quot;1013.41&quot; cy=&quot;794.47&quot; r=&quot;6.94&quot; /&amp;gt;           &amp;lt;circle opacity=&quot;0.9&quot; fill=&quot;#E8C089&quot; cx=&quot;1030.52&quot; cy=&quot;790.24&quot; r=&quot;7.72&quot; /&amp;gt;           &amp;lt;circle opacity=&quot;0.9&quot; fill=&quot;#E8C089&quot; cx=&quot;1050.17&quot; cy=&quot;789.05&quot; r=&quot;8.91&quot; /&amp;gt;           &amp;lt;circle opacity=&quot;0.9&quot; fill=&quot;#E8C089&quot; cx=&quot;1080.06&quot; cy=&quot;785.58&quot; r=&quot;15.78&quot; /&amp;gt;           &amp;lt;path opacity=&quot;0.9&quot; fill=&quot;#E8C089&quot; d=&quot;M1081.98,817.39c-0.62,6.97-6.68,26.13-17.83,34.48c-6.37,4.77-17.31,1.23-22.51,1.91c-18.93,2.47-29.66-4.85-27.64-22.22c0.77-6.67-4.19-16.41,0.12-19.53c5.21-3.77,22.43-11.33,30.39-10.62C1059.07,802.71,1082.96,806.44,1081.98,817.39z&quot; /&amp;gt;         &amp;lt;/g&amp;gt;       &amp;lt;/g&amp;gt;     &amp;lt;/g&amp;gt;     &amp;lt;g id=&quot;top&quot;&amp;gt;       &amp;lt;path id=&quot;shadow&quot; d=&quot;M678.17 511.89c-64.14 43.76-84.01 211.03 17.27 186.91 18.05-4.3 31.78 17.42 49.92 21.28 27.77 5.91 48.9-30.89 77.29-30.41 9.27.16 17.92 4.38 26.92 6.61 21.15 5.25 44.84-1.33 60.25-16.74 16.57-16.56 24.92-42.84 46.87-51.04 6.24-2.33 13.5-3.15 18.04-8.02 3.84-4.12 4.72-10.17 4.76-15.81.19-25.97-14.67-51.51-37.32-64.19-43.51-24.35-.3-62.57-58.55-72.09-20.11-3.29-40.73 5.07-56.52 17.95-5.02 4.09-9.7 8.66-15.26 11.98-22.86 13.63-51.72 2.52-78.31 1.41-26.25-1.09-51.71 9.67-55.36 12.16z&quot; opacity=&quot;.161&quot; /&amp;gt;       &amp;lt;g id=&quot;head&quot;&amp;gt;         &amp;lt;g id=&quot;face&quot;&amp;gt;           &amp;lt;path id=&quot;fluffy-face&quot; fill=&quot;#B8840D&quot; d=&quot;M622.31 469.17c35.97 36.58 78.94 66.56 126.03 87.91 10.34 4.69 21.33 9.05 32.72 8.47 9.42-.48 18.31-4.33 26.68-8.57 54.93-27.81 98.65-76.15 117.54-131.9-9.34-11.93-23.86-18.67-37.91-24.78-14.61-6.35-36.16-18.97-49.98-26.97-21.11-12.21-59.47-14.37-79.69-9.45-29.54 7.18-52.97 22.04-79.91 35.8s-56.58 69.34-55.48 69.49z&quot; /&amp;gt;           &amp;lt;g id=&quot;snout&quot;&amp;gt;             &amp;lt;path fill=&quot;#E8C089&quot; d=&quot;M767.81 563.05c-5.54-3.15-10.94-6.69-15.33-11.3-4.39-4.62-7.74-10.43-8.51-16.76-.71-5.8.77-11.69 3.09-17.05 5.66-13.05 16.78-23.84 30.32-28.2s29.31-1.88 40.29 7.17c2.23 1.84 4.27 3.93 5.84 6.35 1.71 2.64 2.85 5.61 3.75 8.63 2.11 7.04 3.02 14.52 1.85 21.78-1.16 7.25-4.51 14.27-9.96 19.19-6.6 5.96-15.57 8.38-24.17 10.63l-10.71 2.79c-3.89.99-12.36-.9-16.46-3.23z&quot; /&amp;gt;             &amp;lt;path d=&quot;M769.52 536c-1.5-2.13-2.97-4.62-2.49-7.17.36-1.9 1.74-3.43 3.26-4.63 3.98-3.14 9.14-4.54 14.21-4.65 5.07-.11 10.09 1 14.95 2.45 1.39.41 2.93 1 3.48 2.33.54 1.29-.07 2.75-.71 3.99a53.38 53.38 0 01-12.16 15.49c-1.91 1.68-4.16 3.29-6.71 3.25s-10.73-6.66-13.83-11.06z&quot; /&amp;gt;             &amp;lt;path d=&quot;M781.61 547.82c-4 3.25-7.79 7.08-9.55 11.92 2.66.26 4.99-1.73 6.71-3.78 1.72-2.05 3.35-4.4 5.86-5.34.74-.28 1.55-.42 2.33-.25.95.2 1.75.84 2.59 1.32 3.36 1.9 7.99 1.19 10.63-1.64-2.31-2.59-6.33-2.36-9.68-3.28-2.26-.62-4.58-1.84-6.8-1.12-1.27.41-2.26 1.39-2.09 2.17z&quot; /&amp;gt;           &amp;lt;/g&amp;gt;           &amp;lt;g id=&quot;eyes&quot;&amp;gt;             &amp;lt;circle cx=&quot;752.87&quot; cy=&quot;475.88&quot; r=&quot;20.91&quot; /&amp;gt;             &amp;lt;circle cx=&quot;825.53&quot; cy=&quot;475.88&quot; r=&quot;20.91&quot; /&amp;gt;             &amp;lt;g id=&quot;eyes-sparkles&quot;&amp;gt;               &amp;lt;circle id=&quot;sparkle-right&quot; cx=&quot;759.93&quot; cy=&quot;484.96&quot; r=&quot;7.01&quot; fill=&quot;#FFF&quot; /&amp;gt;               &amp;lt;circle id=&quot;sparkle-left&quot; cx=&quot;834.41&quot; cy=&quot;482.88&quot; r=&quot;7.01&quot; fill=&quot;#FFF&quot; /&amp;gt;             &amp;lt;/g&amp;gt;           &amp;lt;/g&amp;gt;           &amp;lt;path id=&quot;fluffy-side-2&quot; fill=&quot;#B8840D&quot; d=&quot;M630.89 452.18c1.83 2.93 3.17 6.14 4.5 9.32 2.55 6.1 5.1 12.21 7.66 18.31 1.35 3.22 2.71 6.52 2.79 10.19-6.97-1.95-14.73-3.83-21.19-.55 2.33-4.3 5.24-8.29 8.62-11.82-4.77 1.61-9.66 2.89-14.6 3.85 2.76-4.18 6.43-7.75 10.69-10.38a68.59 68.59 0 01-14.51 1.2c4.89-2.98 9.79-5.96 14.68-8.93-4.49.4-9.12-.91-12.73-3.61 3.55-1.2 7.1-2.4 10.64-3.6 2.09-.71 4.55-1.87 3.45-3.98z&quot; /&amp;gt;           &amp;lt;path id=&quot;fluffy-side-1&quot; fill=&quot;#B8840D&quot; d=&quot;M905.74 461.35c.64-3.39 1.83-6.66 3.01-9.91 2.26-6.22 4.53-12.43 6.79-18.65 1.2-3.28 2.43-6.63 4.86-9.38 3.8 6.16 8.23 12.8 15.19 14.78a55.35 55.35 0 01-14.35 2.84c4.6 2.06 9.06 4.42 13.34 7.08-4.86 1.2-9.98 1.33-14.9.38 4.13 2.57 7.98 5.58 11.48 8.96l-16.84-3.39c3.57 2.75 6.08 6.85 6.91 11.29-3.42-1.53-6.84-3.05-10.26-4.58-2.01-.9-4.6-1.71-5.23.58z&quot; /&amp;gt;         &amp;lt;/g&amp;gt;         &amp;lt;g id=&quot;ears&quot;&amp;gt;           &amp;lt;path fill=&quot;#B8840D&quot; d=&quot;M757.7 389.23c-6.06-7.49-88.9 40.89-94.96 33.4-8.35-10.32-16.94-21.09-20.14-33.98-5.14-20.67 5.07-42.78 20.66-57.29 9.83-9.14 22.77-16.21 36.12-14.83 11.32 1.17 21.35 8.32 28.46 17.21 7.12 8.89 11.75 19.46 16.29 29.9 3.58 8.21 7.15 16.41 10.73 24.62M914.8 427.68c1.33-9.54-90.59-37-89.26-46.53 1.83-13.15 3.83-26.78 11.06-37.92 11.59-17.87 34.71-25.52 55.96-24.04 13.39.93 27.39 5.55 35.51 16.25 6.88 9.07 8.5 21.28 6.88 32.54-1.63 11.27-6.19 21.87-10.7 32.32l-10.65 24.66&quot; /&amp;gt;           &amp;lt;path d=&quot;M904.64 404.33c.25-.38.51-.76.76-1.14.15.08.23.11.31.15 1.76.63 2.86-1.51 3.1-3.11 1.32-8.6 2.64-17.21 3.95-25.81.9-5.88 1.8-11.83 1.25-17.75s-2.71-11.91-7.09-15.93c-3.03-2.79-6.93-4.47-10.91-5.52-11.8-3.11-24.94-.71-34.88 6.37-9.94 7.08-16.5 18.71-15.11 35.07l-2.07-1.47c16.05 7.48 34.33 11.18 47.66 22.83 1.31 1.14 2.56 2.36 3.97 3.36 3.02 2.14 6.74 3.27 10.46 3.96.15.28-.45.35-.6.07-.15-.28-.13-.67-.39-.84-.24-.18-.47.47-.41-.24z&quot; opacity=&quot;.2&quot; /&amp;gt;           &amp;lt;path d=&quot;M668.95 398.69c-8.62-4.7-12.42-15.61-11.27-25.37 1.15-9.76 6.34-18.55 12.01-26.57 1.66-2.35 3.42-4.7 5.77-6.36 2.97-2.1 6.63-2.92 10.22-3.53 7.14-1.21 15.05-1.58 21 2.54 3.2 2.22 5.48 5.51 7.68 8.72 4.9 7.16 9.8 14.32 14.41 22.11 0 .16 0 .32.56 3.62-21.45 6.49-42.29 14.99-62 25.66 0 .17 0 .33 1.62-.82z&quot; opacity=&quot;.212&quot; /&amp;gt;         &amp;lt;/g&amp;gt;       &amp;lt;/g&amp;gt;     &amp;lt;/g&amp;gt;   &amp;lt;/g&amp;gt;   &amp;lt;g id=&quot;clouds&quot;&amp;gt;     &amp;lt;path id=&quot;cloud-2&quot; d=&quot;M904.43,206.57c0-24.19-26-43.8-58-43.8a72.78,72.78,0,0,0-28,5.43c-20.29-18.19-57.92-30.4-101-30.4-40.24,0-75.74,10.66-96.77,26.9a67.55,67.55,0,1,0-15.53,114.55c9.9,23.25,34.7,39.75,63.75,39.75,24,0,45.06-11.25,57.26-28.25,15.37,7.87,36.13,12.71,59,12.71,47.34,0,85.72-20.69,85.72-46.22A26.44,26.44,0,0,0,868.78,247C889.72,240.39,904.43,224.78,904.43,206.57Z&quot; transform=&quot;translate(1037.17 -51.56)&quot; fill=&quot;#020f0f&quot; opacity=&quot;0.1&quot; /&amp;gt;     &amp;lt;path id=&quot;cloud-1&quot; d=&quot;M1301,255.92c2.89,10.56,29.85,12.38,60.23,4.07a148.3,148.3,0,0,0,25.87-9.63c21.41,2.67,58.55-1.77,99.39-12.94C1524.62,227,1557,213.1,1575,200.56c12.12,1.08,26.79-.29,42-4.47,35.39-9.68,60.46-30.73,56-47s-36.76-21.63-72.15-11.95a135.67,135.67,0,0,0-24.84,9.42c-12.17-7.58-37.66-8.34-65.2-.8-22.74,6.22-41.39,16.6-50.93,27.19-15.51.55-35.78,3.83-57.47,9.76-44.9,12.29-78.82,31.28-75.77,42.42a7,7,0,0,0,3.25,3.92C1310.88,237.34,1298.78,248,1301,255.92Z&quot; transform=&quot;translate(1037.17 -51.56)&quot; fill=&quot;#020f0f&quot; opacity=&quot;0.1&quot; /&amp;gt;     &amp;lt;path id=&quot;cloud-3&quot; d=&quot;M1802.6,873.51c9.78,43.37-45.59,92.81-123.68,110.43a240.24,240.24,0,0,1-70.4,5.66c-42.1,43.76-128.89,86.36-233.9,110.06-98.12,22.14-189,22.54-246.82,5-24.49,23.19-58.2,41.4-97.4,50.24-91,20.53-177-17.06-192.06-84s46.41-137.78,137.39-158.31a221.25,221.25,0,0,1,67.85-4.79c14.74-47.15,68.52-90.37,139.34-106.35,58.47-13.19,114.42-4.62,151,19.16,34.29-22.58,83-42.68,138.72-55.26,115.42-26,217.36-10.06,227.69,35.71a37.69,37.69,0,0,1-1.08,19.54C1753,821,1795.23,840.86,1802.6,873.51Z&quot; transform=&quot;translate(1037.17 -4.61)&quot; fill=&quot;#020f0f&quot; opacity=&quot;0.1&quot; /&amp;gt;   &amp;lt;/g&amp;gt; &amp;lt;/svg&amp;gt;   &amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt;&lt;/code&gt;,
css: &lt;code&gt;body { background-color: #74c2c2; margin: 0; overflow: hidden; } svg { width: 100%; height: 100vh; } #clouds { filter: blur(3px); -webkit-filter: blur(3px); } #shadow { filter: blur(1px); -webkit-filter: blur(1px); }&lt;/code&gt;,
js: `// 1) ripples animate in (opacity 0% -&amp;gt; 100%)
// 2) ripples grow (scale 1.2) and rotate as paws rotate!
// 3) otter eye sparkle moves around
// 4) throughout steps 1 -&amp;gt; 3 the background color is changing slightly between aqua blue and a deeper greenish blue
gsap.to(&quot;#clouds&quot;, {
ease: &quot;sine.out&quot;,
transformOrigin: &quot;50% 50%&quot;,
x: -3000,
y: 600,
duration: 12,
repeat: -1
});&lt;/p&gt;
&lt;p&gt;const tl = gsap.timeline({
defaults: { ease: &quot;sine.out&quot;, duration: 2 },
repeat: -1,
yoyo: true
});&lt;/p&gt;
&lt;p&gt;tl.to(&quot;body&quot;, { duration: 3, backgroundColor: &quot;#4da0a0&quot; })
.to(&quot;#left-top-lines&quot;, { y: -20 }, &quot;&amp;lt;&quot;)
.to(&quot;#right-top-lines&quot;, { y: 20 }, &quot;&amp;lt;&quot;)
.to(&quot;.top-ripple&quot;, { scale: 1.5 }, &quot;&amp;lt;&quot;)
.to(&quot;#left-foot-group&quot;, { y: -50 }, &quot;&amp;lt;&quot;)
.to(&quot;#right-foot-group&quot;, { y: 50 }, &quot;&amp;lt;&quot;)
.to(&quot;.bottom-ripple&quot;, { duration: 1.2, scale: 1.25 }, &quot;&amp;lt;&quot;)
.to(&quot;#eyes-sparkles&quot;, { transformOrigin: &quot;50% 50%&quot;, x: -10, duration: 3 }, &quot;&amp;lt;&quot;);`
};&lt;/p&gt;
&lt;p&gt;I&apos;ve recently been creating more Scalable Vector Graphics (SVG) animations and wanted to walk through my current process for creating SVGs. In particular I will outline how I created the below Sea Bear SVG. If you&apos;re not yet familiar with the SVG file format I&apos;d recommend checking out my article on
&lt;a href=&quot;/blog/getting-started-with-svg-animation/&quot;&gt;getting started with SVG animation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;VanillaCustomSandpack client:only=&quot;react&quot; {...seaBearDemoProps} showEditor={false} showOpenInCodeSandbox /&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Find visual inspiration.&lt;/strong&gt; I usually find a reference photo to use as inspiration for the SVG I&apos;d like to create unless it&apos;s a more abstract concept. I also tend to start off my &amp;lt;a href=&quot;https://www.indigitalcolor.com/&quot;&amp;gt;digital illustrations&amp;lt;/a&amp;gt; with one or multiple reference photos. I am currently working on developing different skills as an artist and use the photo as a visual guide for colors, anatomy and/or proportions. Some of my go-to places for inspiration are &lt;a href=&quot;https://www.pinterest.com/&quot;&gt;Pinterest&lt;/a&gt;, &lt;a href=&quot;https://unsplash.com/&quot;&gt;Unsplash&lt;/a&gt;, &lt;a href=&quot;https://www.pexels.com/&quot;&gt;Pexels&lt;/a&gt;, Google Image, etc. The cool thing about Unsplash and Pexels is that the photos on those sites are generally directly attributable to a specific creator and allow reuse freely.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;center&amp;gt;
&amp;lt;img src=&quot;/media/bear-reference-photo.jpg&quot; width=&quot;400px&quot; /&amp;gt; &amp;lt;br /&amp;gt;
&amp;lt;em&amp;gt;
reference photo used for the bear relaxing in the water (attribution
unknown)
&amp;lt;/em&amp;gt;
&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create a sketch outline&lt;/strong&gt; I find it helpful to take time to come up with a rough sketch before creating the final SVG. I generally create this rough sketch on my iPad in &lt;a href=&quot;https://procreate.art/&quot;&gt;Procreate&lt;/a&gt; to have more control creating the shapes. This step in particular really helps speeds up the process for me in &lt;a href=&quot;https://www.adobe.com/products/illustrator.html&quot;&gt;Adobe Illustrator&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/media/bear-sketch-outlines.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;reference or trace sketch in Illustrator or a vector -art based program&lt;/strong&gt; to create SVG. I then will trace over my previous sketch using an Apple Pencil on iPad with the pen tool in Adobe Illustrator. If the image is less complex or proportions aren&apos;t as pertinent then I can more-so eyeball the initial sketch to convert it to an SVG and use it as a looser guide. Once I have the lines in Illustrator I adjust individual points along each path to ensure that the overall shape and quality of the lines is on par with the desired result.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Color the Vector&lt;/strong&gt;. Once I am happy with the shapes in Illustrator then I begin filling in the colors to get closer to the final image.
&lt;img src=&quot;/media/waterpark-svg.png&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;screenshot of the vector image version of the illustration in Adobe Illustrator on iPad&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;sketch out the animation timeline&lt;/strong&gt; This was something &lt;a href=&quot;https://www.cassie.codes/&quot;&gt;Cassie Evan&lt;/a&gt;&apos;s recommended in her SVG animation workshop which I found helpful for better organizing more complex animations. This involves drawing different versions of the image at different points in timeline. I also note any visual changes I&apos;d like to make during the animation that aren&apos;t reflected in a particular sketch (in order to have loose pseudocode to guide me as I animate).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/media/otter-timeline.png&quot; alt=&quot;&quot; /&gt;
&lt;em&gt;a rough sketch showing how the eyes and water should change over time with the animation along with a note indicating the background color will shift as well&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The pseudocode for my Sea Bear animation based on the above sketch looked like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1) ripples animate in (opacity 0% -&amp;gt; 100%)
2) ripples grow (scale 1.2) and rotate as paws rotate!
3) bear eye sparkle moves around
4) throughout steps 1 -&amp;gt; 3 the background color is changing slightly between aqua blue and a deeper greenish blue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I can use this information as a guide when implementing the code needed to animate the SVG.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Clean up SVG for export&lt;/strong&gt;. Once I have an animation plan it&apos;s time to clean up the SVG so that it is easier to programmatically change the values that are key to the animation. In Adobe Illustrator I make sure all of the layers are named as these layer names will be &quot;id&quot;s in the final exported SVG code and then I also group the layers in Illustrator in the groupings I&apos;d like to animate them in. I then also used a tool like &lt;a href=&quot;https://jakearchibald.github.io/svgomg/&quot;&gt;SVGOMG&lt;/a&gt; to do some clean up to optimize the organized SVG to simplify paths, ensure no images were embedded, etc. These optimizations should reduce the file size of the SVG and make it easier to work with programmatically. CSS Tricks has a good &lt;a href=&quot;https://css-tricks.com/tools-for-optimizing-svg/&quot;&gt;overview of resources for optimizing SVGs&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the cleaned up SVG code to &lt;code&gt;HTML&lt;/code&gt; tab in a &lt;a href=&quot;https://codepen.io/pen/&quot;&gt;new Pen on CodePen&lt;/a&gt; and started coding animation!&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;VanillaCustomSandpack client:only=&quot;react&quot; {...seaBearDemoProps} showEditor showOpenInCodeSandbox/&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;br/&amp;gt;&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Organizing Content with Astro Content Schemas</title><link>https://aboutmonica.com/blog/organizing-astro-content-with-schemas/</link><guid isPermaLink="true">https://aboutmonica.com/blog/organizing-astro-content-with-schemas/</guid><description>With Astro&apos;s Content Collection Schema functionality, you can enforce structured, uniform tags to keep your content organized and SEO-friendly. In this post, we’ll dive into how you can define and validate tags using Astro’s schema system, ensuring every post follows the same tagging conventions. Whether you’re managing a blog, documentation site, or any content-heavy project, this guide will help you streamline your workflow and maintain consistency effortlessly.</description><pubDate>Thu, 06 Mar 2025 18:12:21 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;import Tags from &quot;../../components/Tags.astro&quot;;&lt;/p&gt;
&lt;p&gt;With Astro’s Content Collection Schema functionality, you can enforce structured data like uniform tags to keep your content organized and SEO-friendly. In this post, we&apos;ll dive into how you can define and validate tags using Astro&apos;s schema system, ensuring every article follows the same tagging conventions. Whether you&apos;re managing a blog, documentation site, or any content-heavy project, understanding how to leverage Astro&apos;s Content Collections can help you streamline your workflow and maintain consistency effortlessly.&lt;/p&gt;
&lt;h2&gt;Defining Content Collection Schemas in Astro&lt;/h2&gt;
&lt;p&gt;I recently decided to do some housekeeping on this site. I upgraded to &lt;a href=&quot;https://astro.build/blog/astro-5/&quot;&gt;Astro 5&lt;/a&gt; (specifically Astro 5.4) and refined the tag functionality on this website.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;info&quot;&amp;gt;
{&quot; &quot;}
Astro&apos;s official docs have a helpful guide that covers &lt;a href=&quot;https://docs.astro.build/en/tutorial/5-astro-api/2/&quot;&gt;how to generate tag
pages&lt;/a&gt;.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;After I created the &lt;a href=&quot;/tags&quot;&gt;tags&lt;/a&gt; index for this site I realized there was some
duplication within the tags like &lt;code&gt;Git&lt;/code&gt;, &lt;code&gt;GitHub&lt;/code&gt; and &lt;code&gt;Git/GitHub&lt;/code&gt; depending on
the post.&lt;/p&gt;
&lt;p&gt;&amp;lt;Tags tag={&quot;Git&quot;} /&amp;gt;
&amp;lt;Tags tag={&quot;Git/GitHub&quot;} /&amp;gt;
&amp;lt;Tags tag={&quot;GitHub&quot;} /&amp;gt;&lt;/p&gt;
&lt;p&gt;and in some instances there may have been inconsistent casing for tags that were otherwise the same like:&lt;/p&gt;
&lt;p&gt;&amp;lt;p&amp;gt;
&amp;lt;Tags tag={&quot;JavaScript&quot;} /&amp;gt;
&amp;lt;Tags tag={&quot;javascript&quot;} /&amp;gt;
&amp;lt;/p&amp;gt;&lt;/p&gt;
&lt;h3&gt;Zod Validation in Astro Content Schemas&lt;/h3&gt;
&lt;p&gt;One of the nice features of how Astro manages content is that it has built-in
&lt;a href=&quot;https://zod.dev/&quot;&gt;Zod&lt;/a&gt; support to enforce consistent schemas within a content
collection like restricting valid content tags to a predefined list, including
those compromised of Markdown or MDX files. Zod describes itself as
&quot;TypeScript-first schema validation with static type inference&quot;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Schemas enforce consistent frontmatter or entry data within a collection through Zod validation. A schema guarantees that this data exists in a predictable form when you need to reference or query it. If any file violates its collection schema, Astro will provide a helpful error to let you know.&quot; - &lt;a href=&quot;https://docs.astro.build/en/guides/content-collections/#defining-the-collection-schema&quot;&gt;Astro Content Collections Documentation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With Astro&apos;s content collections
you can define a specific schema to ensure that all of your content contains the necessary data. The configuration for content collections should be stored in
&lt;code&gt;src/content.config.ts&lt;/code&gt;. This is a special file that Astro uses to determine how your content should be structured.&lt;/p&gt;
&lt;p&gt;One of the schemas I have defined for this site is for my &lt;a href=&quot;/bookshelf&quot;&gt;bookshelf&lt;/a&gt; to make sure that the data for each book is an object containing string values for the title, author, and external link. The external link also should be a string that starts with &lt;code&gt;&quot;https://www.google.com/books/edition/&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// import { defineCollection, z } from &quot;astro:content&quot;;
// import { glob } from &quot;astro/loaders&quot;;
const bookshelf = defineCollection({
  loader: glob({ pattern: &quot;**/[^_]*.json&quot;, base: &quot;./src/content/bookshelf&quot; }),
  schema: z.object({
    title: z.string(),
    author: z.string(),
    external_link: z
      .string()
      .startsWith(&quot;https://www.google.com/books/edition/&quot;),
  }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;a sample valid entry would be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;title&quot;: &quot;The Nature of Code&quot;,
  &quot;author&quot;: &quot;Daniel Shiffman&quot;,
  &quot;year&quot;: 2024,
  &quot;external_link&quot;: &quot;https://www.google.com/books/edition/The_Nature_of_Code/Iv_REAAAQBAJ?hl=en&amp;amp;gbpv=0&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However the below object would be invalid as it&apos;s missing the &lt;code&gt;external_link&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;title&quot;: &quot;The Nature of Code&quot;,
  &quot;author&quot;: &quot;Daniel Shiffman&quot;,
  &quot;year&quot;: 2024
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Articles have a different schema than books. Before normalizing the data for the tags the schema defined for this site&apos;s articles looked similar to the below object which indicates that Astro expects the &lt;code&gt;tag&lt;/code&gt; field in all of the posts to be an array of strings and tags are optional. The keys without &lt;code&gt;optional()&lt;/code&gt; in their value like the &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;pubDate&lt;/code&gt; are required for every valid post.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// import { defineCollection, z } from &quot;astro:content&quot;;
// import { glob } from &quot;astro/loaders&quot;;

const blog = defineCollection({
  loader: glob({ pattern: &quot;**/[^_]*.{md,mdx}&quot;, base: &quot;./src/content/blog&quot; }),
  schema: z.object({
    title: z.string(),
    long_title: z.string().optional(),
    description: z.string(),
    featured: z.boolean().optional(),
    pubDate: z
      .string()
      .or(z.date())
      .transform((val) =&amp;gt; new Date(val)),
    tags: z.array(z.string()).optional(),
  }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each key in the schema maps to the frontmatter of my articles. For example the below would be considered valid:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
title: Organizing Content with Astro Content Schemas
pubDate: 2025-03-06T18:12:21.580Z
description: With Astro&apos;s Content Collection Schema functionality, you can enforce structured, uniform tags to keep your content organized and SEO-friendly. In this post, we’ll dive into how you can define and validate tags using Astro’s schema system, ensuring every post follows the same tagging conventions. Whether you’re managing a blog, documentation site, or any content-heavy project, this guide will help you streamline your workflow and maintain consistency effortlessly.
tags: [&quot;Astro&quot;, &quot;TypeScript&quot;]
---
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Using Enums in Zod for Predefined Valid Content&lt;/h3&gt;
&lt;p&gt;The inconsistency within the tagging naming conventions revealed that limiting the tags to an array of strings was not restrictive enough so I decided to create a predefined list of valid topics and codified this by defining a Zod enum of valid &lt;code&gt;topics&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The Zod enum looks something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// import { z } from &quot;astro:content&quot;;
const topics = z.enum([
  &quot;Astro&quot;,
  &quot;Animation&quot;,
  &quot;Community&quot;,
  &quot;Creative Coding&quot;,
  &quot;CSS&quot;,
  &quot;Developer Productivity&quot;,
  &quot;Functional Programming&quot;,
  &quot;Git/GitHub&quot;,
  &quot;JavaScript&quot;,
  &quot;MDX&quot;,
  &quot;Netlify&quot;,
  &quot;NextJS&quot;,
  &quot;p5⁎js&quot;,
  &quot;React&quot;,
  &quot;VSCode&quot;,
]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and once the &lt;code&gt;topics&lt;/code&gt; enum was defined it could be used in the schema for articles to expect that valid tags existed within &lt;code&gt;topics&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- tags: z.array(z.string()).optional()
+ tags: z.array(topics).optional()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this set up I can easily view the list of valid tags and extend it as needed.&lt;/p&gt;
&lt;h3&gt;What happens if content is not aligned with the Astro schema?&lt;/h3&gt;
&lt;p&gt;Now that valid tags are restricted to a predefined list of topics whenever a new post is created it must match the schema or Astro will throw an error. Attempting to include an invalid tag results in an error that must be resolved before the app will build and the error looks something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ERROR] [UnhandledRejection] Astro detected an unhandled rejection.
Here&apos;s the stack trace:
InvalidContentEntryDataError: blog → tagging-schema-in-astro data does not match collection schema.
tags.1: Invalid enum value. Expected &apos;Astro&apos; | &apos;Animation&apos;| &apos;Community&apos; | &apos;Creative Coding&apos; | &apos;CSS&apos; | &apos;Developer Productivity&apos; | &apos;Functional Programming&apos;| &apos;Git/GitHub&apos; | &apos;JavaScript&apos; |  &apos;MDX&apos; | &apos;Netlify&apos; | &apos;NextJS&apos; | &apos;p5⁎js&apos; | &apos;React&apos; |&apos;VSCode&apos; | &apos;WebMention&apos;, received &apos;Git&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Astro&apos;s &lt;code&gt;InvalidContentEntryDataError&lt;/code&gt; errors can help developers quickly identify any content that has invalid data such as tags. In my case, the errors saved me time since there was a limited number of articles that needed to be edited and I was manually (or finding/replacing) to make sure all content was valid with the new schema.&lt;/p&gt;
&lt;h3&gt;Enforcing Character Limits with Zod&lt;/h3&gt;
&lt;p&gt;I am looking forward to further refining the schemas that I use in Astro and exploring more of Zod&apos;s functionality. In addition to normalizing tags, Zod can define other specific data types. For example, I am considering updating my schema to distinguish how a valid &lt;code&gt;long_title&lt;/code&gt; and &lt;code&gt;title&lt;/code&gt; diverge by using &lt;code&gt;.max(chars)&lt;/code&gt; from Zod. When a &lt;code&gt;long_title&lt;/code&gt; is defined for a post it&apos;s displayed throughout my site, however, a shorter &lt;code&gt;title&lt;/code&gt; might be better for meta images. Every article must have a valid &lt;code&gt;title&lt;/code&gt; but the &lt;code&gt;long_title&lt;/code&gt; is an optional field that can be used, when space is not limited, to convey more context than the &lt;code&gt;47&lt;/code&gt;-character limit set for titles below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;long_title: z.string().optional()
title: z.string().max(47),
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Zod can also be configured to enforce a &lt;code&gt;.min(chars)&lt;/code&gt; or an exact &lt;code&gt;.length(chars)&lt;/code&gt;. I encourage you to explore the &lt;a href=&quot;https://zod.dev/&quot;&gt;Zod documentation&lt;/a&gt; and the &lt;a href=&quot;https://docs.astro.build/en/guides/content-collections/#defining-the-collection-schema&quot;&gt;Astro Content Collection Schema documentation&lt;/a&gt; for more details and ideas.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Overcoming the Goldilocks Complex</title><link>https://aboutmonica.com/blog/overcoming-the-goldilocks-complex/</link><guid isPermaLink="true">https://aboutmonica.com/blog/overcoming-the-goldilocks-complex/</guid><description>Lately, I’ve felt a bit stuck with my progress on learning how to code as I think I’m comfortable with the fundamentals. As I’m starting to immerse myself in coding, reading article’s like the Viking…</description><pubDate>Tue, 18 Apr 2017 00:12:34 GMT</pubDate><content:encoded>&lt;h4&gt;Learning how to code — when you’re stuck in-between novice and expert.&lt;/h4&gt;
&lt;p&gt;&amp;lt;figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/overcoming-the-goldilocks-complex-0.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;figcaption&amp;gt;Image of three bears chasing Goldilocks. Courtesy of &amp;lt;a href=&quot;https://commons.wikimedia.org&quot; class=&quot;figcaption-link&quot;&amp;gt;Wikimedia Commons&amp;lt;/a&amp;gt;&amp;lt;/figcaption&amp;gt;&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“I’m over 101 courses. I want to try 201 and 301.” — thoughts on the lack of classes for self-taught developers between novice and expert.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Lately, I’ve felt a bit stuck with my progress on learning how to code as I think I’m comfortable with the fundamentals. As I’m starting to immerse myself in coding, reading articles like the &lt;a href=&quot;https://medium.com/u/d5c1b9920ab&quot;&gt;Viking Code School&lt;/a&gt;’s &lt;a href=&quot;https://www.vikingcodeschool.com/posts/why-learning-to-code-is-so-damn-hard&quot;&gt;“Why Learning to Code is So Damn Hard”&lt;/a&gt; strongly resonate with me. I recommend checking out that article if you are currently transitioning from breadth knowledge of an area to depth knowledge of a more specific domain.&lt;/p&gt;
&lt;p&gt;I have picked up a habit of attending technical &lt;a href=&quot;https://medium.com/u/49cf3ccf2c84&quot;&gt;Meetup&lt;/a&gt; events regularly, but I find myself pausing when signing up for a class or workshop as I am afraid I will feel like Goldilocks, disappointed by a class that is not the right fit. The material in the courses I’ve come across is often &lt;em&gt;too&lt;/em&gt; elementary or &lt;em&gt;too&lt;/em&gt; advanced.&lt;/p&gt;
&lt;p&gt;After taking a handful of classes and having a strong grasp on the essentials, it becomes more important to ask yourself questions about your goals before jumping from one class to the next.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Why am I interested in taking this class? Are the perquisites too advanced or too rudimentary for me? Will the material feel repetitive and if so, will the value outweigh the initial boredom? Would it be more beneficial for me to roll up my sleeves and use my current knowledge and skills to tackle a challenging project?&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;I’ve learned the fundamentals of programming. Now what?&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;Contribute to open-source software (OSS)&lt;/strong&gt;.&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/u/8df3bf3c40ae&quot;&gt;GitHub&lt;/a&gt; and &lt;a href=&quot;https://medium.com/u/68f5136d3254&quot;&gt;GitLab&lt;/a&gt; have large communities of developers contributing to various projects across every sector and technology stack. I am still learning a lot about software development but I recently contributed to &lt;a href=&quot;https://github.com/code-corps&quot;&gt;CodeCorps&lt;/a&gt;, a friendly community that uses &lt;a href=&quot;https://medium.com/u/d9dc4b547387&quot;&gt;EmberJS&lt;/a&gt; and works on open source for social good. The great thing about OSS is that &lt;em&gt;anyone&lt;/em&gt; can contribute.&lt;/p&gt;
&lt;p&gt;If you need help getting started in OSS check out &lt;a href=&quot;http://yourfirstpr.github.io&quot;&gt;Your First PR&lt;/a&gt; and &lt;a href=&quot;http://up-for-grabs.net/#/&quot;&gt;Up for Grabs&lt;/a&gt; which are both sites that help surface open issues that work well for individuals new to contributing. Additionally, &lt;a href=&quot;https://medium.com/u/8b318225c16a&quot;&gt;Free Code Camp&lt;/a&gt; has a nice Medium article on &lt;a href=&quot;https://medium.freecodecamp.com/how-to-land-your-first-open-source-contribution-from-your-browser-in-15-minutes-756d9bbf81ad#.wtu9fzmrl&quot;&gt;landing your first open-source contribution, from your browser, in 15 minutes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you are more seasoned in open-source software or just curious about the landscape, check out this piece on &lt;a href=&quot;https://medium.com/@hoffa/the-top-github-projects-per-country-92c275e19409&quot;&gt;top GitHub projects in each country&lt;/a&gt;. It shows interesting patterns about the most popular types of open-source software, and it may help you find the next project you can contribute to.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/overcoming-the-goldilocks-complex-1.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;figcaption&amp;gt;Image via &amp;lt;a href=&quot;https://giphy.com&quot; class=&quot;figcaption-link&quot;&amp;gt;Giphy&amp;lt;/a&amp;gt;&amp;lt;/figcaption&amp;gt;&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;Share what you learned (even if it is just for your future self).&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Be sure to document your learnings. You gain a better grasp of the material by teaching someone what you know. Presenting the information to others — via blog or in person — will reinforce what you’ve learned and allows you to prioritize the information in a meaningful way that you otherwise might not have.&lt;/p&gt;
&lt;p&gt;I recently had to call myself out for imposter syndrome — I seriously considered signing up for a college-level Python class where I already knew ~90% of the content. It wasn’t until after I requested a copy of the syllabus that I realized I’d be disappointed. If you have a strong grasp on the main topics a course and have satisfied any applicable prerequisites, then perhaps you could gain more as a TA than as a student. In addition to teaching others, written documentation in a README or blog is a great way to allow you to look back at your learnings. &lt;a href=&quot;https://www.mobomo.com/2015/09/writing-code-for-your-future-self/&quot;&gt;Your future self will thank you for writing meaningful comments in your code&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;Challenge yourself.&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Don’t be afraid to attend conferences and technical events that are outside the comfort of your current scope.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/overcoming-the-goldilocks-complex-2.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;figcaption&amp;gt;Image courtesy of &amp;lt;a href=&quot;https://medium.com/u/fbfa235a954c&quot; class=&quot;figcaption-link&quot;&amp;gt;O’Reilly Media&amp;lt;/a&amp;gt;&amp;lt;/figcaption&amp;gt;&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Earlier this month I was awarded an &lt;a href=&quot;https://medium.com/u/fbfa235a954c&quot;&gt;O’Reilly Media&lt;/a&gt; scholarship to attend O’Reilly’s Software Architecture Conference in New York City. The “Who Should Attend” section of the conference advertisement reflected their openness to novice techies, calling for “Aspiring software architects seeking the skills necessary to move up”. If you identify as a member of an underrepresented group within the tech community, apply &lt;a href=&quot;http://www.oreilly.com/conferences/diversity-application.csp&quot;&gt;here&lt;/a&gt; for a scholarship to an upcoming O’Reilly conference.&lt;/p&gt;
&lt;p&gt;My biggest takeaways from the Software Architecture conference included learning first hand from &lt;a href=&quot;https://medium.com/u/991272e72e68&quot;&gt;Google Developers&lt;/a&gt; about how Accelerated Mobile Pages (AMP) and progressive web apps (PWA) can increase user engagement and increase second-visit performance in addition to the technical ways cutting-edge technology such as AMP can be implemented. I was able to bring that valuable information back to my job.&lt;/p&gt;
&lt;p&gt;I found out about this scholarship opportunity through Elizabeth Ferrao of &lt;a href=&quot;https://medium.com/u/45b39be42e08&quot;&gt;Women Who Code NYC&lt;/a&gt; and &lt;a href=&quot;https://medium.com/u/601440fca2a2&quot;&gt;Jessica Waite&lt;/a&gt; of &lt;a href=&quot;https://medium.com/u/52b98d67b1db&quot;&gt;Hello World&lt;/a&gt;. Being involved in communities like &lt;a href=&quot;https://medium.com/u/f05962335e24&quot;&gt;Women Who Code&lt;/a&gt;, &lt;a href=&quot;https://medium.com/u/94d47f086dce&quot;&gt;Ladies Storm Hackathons&lt;/a&gt;, and &lt;a href=&quot;https://medium.com/u/285c3e3d8674&quot;&gt;Ladies Get Paid&lt;/a&gt; is a great way to find out about local opportunities and meet up with other women in the tech industry.&lt;/p&gt;
&lt;p&gt;In addition to the technical topics that I was exposed to at the conference, I was able to engage in a discussion with other scholarship recipients about how important it is to speak at conferences even if you don’t feel like an “Expert” on a subject. There is some realm where you are the expert — sharing valuable information from personal experience is a great way to be an expert (e.g., How I overcame imposter syndrome, How I broke into tech without a CS degree, My experience as a bootcamp grad, etc.)&lt;/p&gt;
&lt;p&gt;Despite initially feeling like I was &lt;em&gt;just&lt;/em&gt; an aspiring software architect, I was able to walk away with invaluable information from not only the various speakers but also the other attendees.&lt;/p&gt;
&lt;p&gt;If learning how to code has taught me anything it is that learning &lt;em&gt;how&lt;/em&gt; to learn is crucial to advancing. To conclude this article, I want to share an important quote from a &lt;a href=&quot;https://medium.com/u/7ddfe8a6bc4f&quot;&gt;Harvard Business Review&lt;/a&gt; article about how to get better at &lt;a href=&quot;https://hbr.org/2016/03/learning-to-learn&quot;&gt;Learning to learn&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;It speaks to the uncomfortable experience that accompanies letting go of the training wheels:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“I’m not talking about relaxed armchair or even structured classroom learning. I’m talking about resisting the bias against doing new things, scanning the horizon for growth opportunities, and pushing yourself to acquire radically different capabilities — while still performing your job. That requires a willingness to experiment and become a novice again and again: an extremely discomforting notion for most of us.” — &lt;a href=&quot;https://medium.com/u/23e95c143295&quot;&gt;Erika Andersen&lt;/a&gt;, &lt;a href=&quot;https://medium.com/u/7ddfe8a6bc4f&quot;&gt;Harvard Business Review&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;Do you have any tips for leaping from a Novice coder to Expert programmer? If so, I’d love to hear your tips.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you enjoyed reading this article consider tapping the clap button 👏. Wanna see more of my work? Check out&lt;/em&gt; &lt;a href=&quot;https://github.com/M0nica/&quot;&gt;&lt;em&gt;my GitHub&lt;/em&gt;&lt;/a&gt; &lt;em&gt;to view my code and learn more about my development experience at&lt;/em&gt; &lt;a href=&quot;http://aboutmonica.com&quot;&gt;&lt;em&gt;http://aboutmonica.com&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Egghead Podcast: &quot;Personal Growth From Open-Source And Meetups&quot;</title><link>https://aboutmonica.com/blog/personal-growth-egghead-podcast-monica-powell/</link><guid isPermaLink="true">https://aboutmonica.com/blog/personal-growth-egghead-podcast-monica-powell/</guid><description>This episode of the Egghead podcast gives listeners a better glimpse into my journey into landing my first engineering role and more.</description><pubDate>Sun, 22 Dec 2019 02:43:13 GMT</pubDate><content:encoded>&lt;p&gt;I recently spent some time talking with Joel Hooks from &lt;a href=&quot;https://egghead.io/?af=9b5hrz&quot;&gt;Egghead.io&lt;/a&gt; about my journey to landing my first engineering role, attending and organizing meetups and contributing to open-source. Give it a listen if you&apos;d like to learn more about my journey into tech.&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe height=&quot;200px&quot; width=&quot;100%&quot; frameborder=&quot;no&quot; scrolling=&quot;no&quot; seamless src=&quot;https://player.simplecast.com/f471c68a-015a-4c53-8fb3-568518141c4b?dark=false&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>How I automatically created a Twitter List of FreeCodeCampers in 5 minutes</title><link>https://aboutmonica.com/blog/programmatically-create-twitter-list/</link><guid isPermaLink="true">https://aboutmonica.com/blog/programmatically-create-twitter-list/</guid><description>Using Twython Twitter API wrapper to add users to a Twitter List</description><pubDate>Wed, 17 Jan 2018 17:11:11 GMT</pubDate><content:encoded>&lt;h2&gt;Using Twython Twitter API wrapper to add users to a Twitter List&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/2400/1*mUQDjnECZGSncv_imkD3yA.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We are going to create a Python script that will automatically search Twitter
for individuals who use the &lt;strong&gt;#freeCodeCamp&lt;/strong&gt; hashtag and add them to a Twitter
list of &quot;FreeCodeCampers&quot;. &lt;a href=&quot;https://help.twitter.com/en/using-twitter/twitter-lists&quot;&gt;Twitter
lists&lt;/a&gt; are a way to
curate a group of individuals on Twitter and collect all of their tweets in a
stream, without having to follow each individual accounts. Twitter lists can
contain up to 5,000 individual Twitter accounts.&lt;/p&gt;
&lt;p&gt;We can accomplish this by doing the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Installing the necessary Python packages&lt;/li&gt;
&lt;li&gt;Registering an application with Twitter&lt;/li&gt;
&lt;li&gt;Generating and accessing our Twitter credentials&lt;/li&gt;
&lt;li&gt;Making Twitter
&lt;a href=&quot;https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets&quot;&gt;Search&lt;/a&gt;
and
&lt;a href=&quot;https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-list&quot;&gt;List&lt;/a&gt;
API calls&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So lets get started.&lt;/p&gt;
&lt;h3&gt;1. Installing the necessary Python packages&lt;/h3&gt;
&lt;p&gt;Create a file named &lt;code&gt;addToFreeCodeCampList.py&lt;/code&gt;, that will contain our main
script and then import two Python modules into this file:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Import Config:&lt;/strong&gt; In the same directory as our&lt;code&gt;addToFreeCodeCampList.py&lt;/code&gt;
script, create a file named &lt;code&gt;config.py&lt;/code&gt; that stores our confidential Twitter API
credentials. We are going to import our API credentials from that file into our
&lt;code&gt;addToFreeCodeCampList.py&lt;/code&gt; script by including the line &lt;code&gt;import config&lt;/code&gt;. Twitter
requires a valid API key, API secret, access token and token secret for all API
requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Import Twython:&lt;/strong&gt; &lt;a href=&quot;https://github.com/ryanmcgrath/twython&quot;&gt;Twython&lt;/a&gt; is a
Python wrapper for the Twitter API that makes it easier to programmatically
access and manipulate data from Twitter using Python. We can import Twython with
the following line &lt;code&gt;from twython import Twython, TwythonError&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your &lt;code&gt;addToFreeCodeCampList.py&lt;/code&gt; script should now look like this.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    import config
    from twython import Twython, TwythonError
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Registering an application with Twitter&lt;/h3&gt;
&lt;p&gt;We need to authenticate our application in order to access the Twitter API. You
need to have a Twitter account in order to access &lt;a href=&quot;https://apps.twitter.com/&quot;&gt;Twitter&apos;s Application
Management site&lt;/a&gt;. The Application Management site is
where you can view/edit/create API keys, API secrets, access tokens and token
secrets.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In order to create these credentials, we need to create a Twitter application.
Go to the Application Management site and click on &quot;Create New App&quot;. This should
direct you to a page that looks similar to the one below.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1600/1*H8TiOR6qnIXo_sNoRb7OGw.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fill out of the required fields and click on &quot;Create your Twitter
application&quot;. You will then be redirected to a page with details about your
application.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3. Generating and accessing our Twitter credentials&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Click on the tab that says &quot;Keys and Access Tokens&quot; and copy the &quot;Consumer Key
(API Key)&quot; and &quot;Consumer Secret (API Secret)&quot; into the &lt;code&gt;config.py&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Scroll down to the bottom of the page and click on &quot;Create my access token&quot;.
Copy the generated &quot;Access Token&quot; and &quot;Access Token Secret&quot; into the &lt;code&gt;config.py&lt;/code&gt;
file.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol&gt;
&lt;li&gt;Currently, all of our Twitter credentials live inside our &lt;code&gt;config.py&lt;/code&gt; file
and we&apos;ve imported &lt;code&gt;config&lt;/code&gt; into our &lt;code&gt;addToFreeCodeCampList.py&lt;/code&gt; file. However,
we have not actually passed any information between the files.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&apos;s change that by creating a Twython object and passing in the necessary API
key, API secrets and API token from our &lt;code&gt;config.py&lt;/code&gt; file with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    twitter = Twython(config.api_key, config.api_secret, config.access_token, config.token_secret)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;addToFreeCodeCampList.py&lt;/code&gt; file should now look similar to this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  import config
  from twython import Twython, TwythonError
  # create a Twython object by passing the necessary secret passwords
  twitter = Twython(config.api_key, config.api_secret, config.access_token, config.token_secret)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Making Twitter Search and List API calls&lt;/h3&gt;
&lt;h4&gt;Let&apos;s make an API call to search Twitter and return the 100 most recent tweets&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;        # return tweets containing #FreeCodeCamp
        response = twitter.search(q=&apos;&quot;#FreeCodeCamp&quot; -filter:retweets&apos;, result_type=&quot;recent&quot;, count=100)
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;Look at the tweets returned from our search&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt; # for each tweet returned from search of #FreeCodeCamp

 for tweet in response[&apos;statuses&apos;]:

 # print tweet info if needed for debugging

 print(tweet)
 print(tweet[&apos;user&apos;][&apos;screen_name&apos;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A single tweet returned by this API call looks like this in JSON:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; {&apos;created_at&apos;: &apos;Sun Dec 24 00:23:05 +0000 2017&apos;, &apos;id&apos;: 944725078763298816, &apos;id_str&apos;: &apos;944725078763298816&apos;, &apos;text&apos;: &apos;Why is it so hard to wrap my head around node/express. Diving in just seems so overwhelming. Templates, forms, post…
  &apos;truncated&apos;: True, &apos;entities&apos;: {&apos;hashtags&apos;: [], &apos;symbols&apos;: [], &apos;user_mentions&apos;: [], &apos;urls&apos;: [{&apos;url&apos;: &apos;https://t.co/ae52rro63i&apos;, &apos;expanded_url&apos;: &apos;https://twitter.com/i/web/status/944725078763298816&apos;, &apos;display_url&apos;: &apos;twitter.com/i/web/status/9…&apos;, &apos;indices&apos;: [117, 140]}]}, &apos;metadata&apos;: {&apos;iso_language_code&apos;: &apos;en&apos;, &apos;result_type&apos;: &apos;recent&apos;}, &apos;source&apos;: &apos;&amp;lt;a href=&quot;http://twitter.com&quot; rel=&quot;nofollow&quot;&amp;gt;Twitter Web Client&amp;lt;/a&amp;gt;&apos;, &apos;in_reply_to_status_id&apos;: None, &apos;in_reply_to_status_id_str&apos;: None, &apos;in_reply_to_user_id&apos;: None, &apos;in_reply_to_user_id_str&apos;: None, &apos;in_reply_to_screen_name&apos;: None, &apos;user&apos;: {&apos;id&apos;: 48602981, &apos;id_str&apos;: &apos;48602981&apos;, &apos;name&apos;: &apos;Matt Huberty&apos;, &apos;screen_name&apos;: &apos;MattHuberty&apos;, &apos;location&apos;: &apos;Oxford, MS&apos;, &apos;description&apos;: &quot;I&apos;m a science and video game loving eagle scout with a Microbio degree from UF. Nowadays I&apos;m working on growing my tutoring business at Ole Miss. Link below!&quot;, &apos;url&apos;: &apos;https://t.co/dfuqNNoBYZ&apos;, &apos;entities&apos;: {&apos;url&apos;: {&apos;urls&apos;: [{&apos;url&apos;: &apos;https://t.co/dfuqNNoBYZ&apos;, &apos;expanded_url&apos;: &apos;http://www.thetutorcrew.com&apos;, &apos;display_url&apos;: &apos;thetutorcrew.com&apos;, &apos;indices&apos;: [0, 23]}]}, &apos;description&apos;: {&apos;urls&apos;: []}}, &apos;protected&apos;: False, &apos;followers_count&apos;: 42, &apos;friends_count&apos;: 121, &apos;listed_count&apos;: 4, &apos;created_at&apos;: &apos;Fri Jun 19 04:00:44 +0000 2009&apos;, &apos;favourites_count&apos;: 991, &apos;utc_offset&apos;: -28800, &apos;time_zone&apos;: &apos;Pacific Time (US &amp;amp; Canada)&apos;, &apos;geo_enabled&apos;: False, &apos;verified&apos;: False, &apos;statuses_count&apos;: 199, &apos;lang&apos;: &apos;en&apos;, &apos;contributors_enabled&apos;: False, &apos;is_translator&apos;: False, &apos;is_translation_enabled&apos;: False, &apos;profile_background_color&apos;: &apos;C0DEED&apos;, &apos;profile_background_image_url&apos;: &apos;http://abs.twimg.com/images/themes/theme1/bg.png&apos;, &apos;profile_background_image_url_https&apos;: &apos;https://abs.twimg.com/images/themes/theme1/bg.png&apos;, &apos;profile_background_tile&apos;: False, &apos;profile_image_url&apos;: &apos;http://pbs.twimg.com/profile_images/777294001598758912/FVOIrnb4_normal.jpg&apos;, &apos;profile_image_url_https&apos;: &apos;https://pbs.twimg.com/profile_images/777294001598758912/FVOIrnb4_normal.jpg&apos;, &apos;profile_banner_url&apos;: &apos;https://pbs.twimg.com/profile_banners/48602981/1431670621&apos;, &apos;profile_link_color&apos;: &apos;1DA1F2&apos;, &apos;profile_sidebar_border_color&apos;: &apos;C0DEED&apos;, &apos;profile_sidebar_fill_color&apos;: &apos;DDEEF6&apos;, &apos;profile_text_color&apos;: &apos;333333&apos;, &apos;profile_use_background_image&apos;: True, &apos;has_extended_profile&apos;: True, &apos;default_profile&apos;: True, &apos;default_profile_image&apos;: False, &apos;following&apos;: False, &apos;follow_request_sent&apos;: False, &apos;notifications&apos;: False, &apos;translator_type&apos;: &apos;none&apos;}, &apos;geo&apos;: None, &apos;coordinates&apos;: None, &apos;place&apos;: None, &apos;contributors&apos;: None, &apos;is_quote_status&apos;: False, &apos;retweet_count&apos;: 1, &apos;favorite_count&apos;: 0, &apos;favorited&apos;: False, &apos;retweeted&apos;: False, &apos;lang&apos;: &apos;en&apos;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Add Tweet-ers to our Twitter list&lt;/h4&gt;
&lt;p&gt;In order to add the author of the tweet to our Twitter list we need the username
associated with the tweet &lt;code&gt;tweet[&apos;user&apos;][&apos;screen_name&apos;]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Let&apos;s try to add the users from these tweets to our Twitter list
&quot;FreeCodeCampers&quot;. I created my Twitter list at
&lt;a href=&quot;https://twitter.com/indigitalcolor/lists/freecodecampers&quot;&gt;https://twitter.com/indigitalcolor/lists/freecodecampers&lt;/a&gt;
which means for my script the slug is &lt;code&gt;freecodecampers&lt;/code&gt; and the
&lt;code&gt;owner_screen_name&lt;/code&gt; is mine, waterproofheart.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for tweet in response[&apos;statuses&apos;]:

# try to add each user who has tweeted the hashtag to the list
try:
twitter.add_list_member(slug=&apos;YOUR_LIST_SLUG&apos;, owner_screen_name=&apos;YOUR_USERNAME&apos;, screen_name= tweet[&apos;user&apos;][&apos;screen_name&apos;])

#if for some reason Twython can&apos;t add user to the list print exception message
except TwythonError as e:
print(e)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can create your own Twitter list by navigating to your Twitter profile,
clicking on &quot;Lists&quot; on desktop and clicking on the right hand side to &quot;Create
new list&quot;. View the &lt;a href=&quot;https://help.twitter.com/en/using-twitter/twitter-lists&quot;&gt;official Twitter List
documentation&lt;/a&gt; for more
information.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1600/1*TPUBuOqUwh_WXUNrUu6MyA.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can test your script by running &lt;code&gt;python addToFreeCodeCampList.py&lt;/code&gt; in the
terminal.&lt;/p&gt;
&lt;p&gt;My final script looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import config

from twython import Twython, TwythonError

# create a Twython object by passing the necessary secret passwords
twitter = Twython(config.api_key, config.api_secret, config.access_token, config.token_secret)

# return tweets containing #FreeCodeCamp
response = twitter.search(q=&apos;&quot;#FreeCodeCamp&quot;  -filter:retweets&apos;, result_type=&quot;recent&quot;, count=100)


# for each tweet returned from search of #FreeCodeCamp
for tweet in response[&apos;statuses&apos;]:
 # print each username if needed for debugging
 # print(tweet[&apos;user&apos;][&apos;screen_name&apos;])

 # try to add each user who has tweeted the hashtag to the list
 try:
     twitter.add_list_member(slug=&apos;YOUR_LIST_SLUG&apos;, owner_screen_name=&apos;YOUR_SCREEN_NAME&apos;, screen_name= tweet[&apos;user&apos;][&apos;screen_name&apos;])
 except TwythonError as e:
     print(e)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This script can be set to automatically run locally or remotely via a &lt;a href=&quot;https://en.wikipedia.org/wiki/Cron&quot;&gt;cron
job&lt;/a&gt; which allows tasks to be performed at a
set schedule.&lt;/p&gt;
&lt;p&gt;Feel free to &lt;a href=&quot;https://twitter.com/waterproofheart&quot;&gt;tweet at me&lt;/a&gt;
if you have any questions, suggestions or want to share how you modified this script!&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Keeping Server-Side Rendering Cool With React Hydration</title><link>https://aboutmonica.com/blog/server-side-rendering-react-hydration-best-practices/</link><guid isPermaLink="true">https://aboutmonica.com/blog/server-side-rendering-react-hydration-best-practices/</guid><description>The purpose of this article is to share some helpful things to keep in mind to render a seamless experience as a Server-Side Rendered (SSR) site transitions from a window-less (server) environment to a browser.</description><pubDate>Fri, 14 Aug 2020 20:34:26 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;Server-side rendering can be powerful but it does require thinking in multiple contexts so it&apos;s important to be familiar with some of the common gotchas when developing Server-Side Rendered websites. This article is a written version of a talk I gave at &lt;a href=&quot;https://www.reactrally.com/&quot;&gt;React Rally&lt;/a&gt; 2020, where I shared some helpful things to keep in mind to render a seamless experience as a Server-Side Rendered (SSR) site transitions from a window-less (server) environment to a browser.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout&amp;gt;
A version of &quot;Keeping Server-Side Rendering Cool With React Hydration&quot; is also
available at
&amp;lt;a href=&quot;https://github.com/M0nica/react-ssr-hydration-talk&quot;&amp;gt;
{&quot; &quot;}
GitHub/m0nica/react-ssr-hydration-talk
&amp;lt;/a&amp;gt;
.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h2&gt;What is Server-Side Rendering (SSR)?&lt;/h2&gt;
&lt;p&gt;Let&apos;s take a step back. First, what is server-side rendering? When a server generates the initial HTML that loads in a browser. Frameworks like &lt;a href=&quot;https://nextjs.org/&quot;&gt;NextJS&lt;/a&gt; and &lt;a href=&quot;https://www.gatsbyjs.com/&quot;&gt;GatsbyJS&lt;/a&gt; support SSR out of the box. Server Side Rendered applications tend to initially load content faster and lead to higher SEO ranking than their Client-Side Rendered counterparts.&lt;/p&gt;
&lt;p&gt;There are different types of server-side rendering for example server-side rendering can be used to render every single page request or only the initial page request. &lt;a href=&quot;https://nextjs.org/docs/basic-features/pages#two-forms-of-pre-rendering&quot;&gt;NextJS offers two forms of server-side rendering&lt;/a&gt;. You may be familiar with &lt;a href=&quot;https://github.com/facebook/create-react-app&quot;&gt;Create React App&lt;/a&gt;, a default React app boilerplate which does not come with SSR functionality configured out of the box.&lt;/p&gt;
&lt;h2&gt;What is Client-Side Rendering (CSR)?&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/media/ssr-hydration/enable-javascript.png&quot; alt=&quot;Blank page in browser saying &amp;quot;You need to enable JavaScript to run this app&amp;quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In contrast to Server-Side Rendering, a website that only supports Client-Side Rendering requires visitors to have JavaScript enabled to view content on the site. Often visitors will see a largely blank page when visiting a Client-Side rendered application if they do not have JavaScript enabled.&lt;/p&gt;
&lt;p&gt;If you look at the DOM in the developer tools of a Create React App (or client-side only rendered application) you&apos;ll notice very little HTML markup in the DOM. The markup might resemble something like the below code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;!-- SEO/Metadata here --&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div&amp;gt;You need to enable JavaScript to run this app.&amp;lt;/div&amp;gt;
    &amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;script&amp;gt;
      &amp;lt;!-- all of the JavaScript --&amp;gt;
    &amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;/static/js/2.6158a3d8.chunk.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src=&quot;/static/js/main.ba831a9f.chunk.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Generally this markup will include the root where React is injected, a message saying you need to enable JavaScript to run the app as well as script tags that link to the JavaScript that needs to be loaded to hydrate the page.&lt;/p&gt;
&lt;h2&gt;Overview of SSR (in static context)&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/media/ssr-hydration/hydration-steps-with-link.png&quot; alt=&quot;step by step illustration walking through react hydration steps&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Let&apos;s walk through what happens in Server-Side Rendered applications like NextJS or Gatsby when all the pages for the site are statically generated at once in the server.&lt;/p&gt;
&lt;p&gt;First, you write the site in React ⚛️ then Gatsby or Next (Static Site Generation) creates a production build of your site using ReactDOMServer, a React server-side API to generate HTML from React. When someone visits your website the first thing they&apos;ll see is the HTML generated from the server. JavaScript then loads after the initial page load and the &lt;a href=&quot;https://reactjs.org/docs/react-dom.html#hydrate&quot;&gt;ReactDOM.hydrate() API&lt;/a&gt; kicks in to hydrate the HTML page that was rendered from the server with JavaScript. After Hydration the &lt;a href=&quot;https://reactjs.org/docs/reconciliation.html&quot;&gt;React reconciler APIs&lt;/a&gt; take over and the site becomes interactive.&lt;/p&gt;
&lt;h2&gt;Toggling JavaScript: SSR vs. CSR&lt;/h2&gt;
&lt;p&gt;Let&apos;s compare how a Server-Side and Client-Side rendered applications appear when JavaScript is enabled or disabled. For these two examples, I used Gatsby and Create React App for these technologies.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/ssr-hydration/gatsby-toggle-javascript.gif&quot; alt=&quot;gif of enabling/disabling JavaScript on Gatsby site&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The above image is of a Gatsby site, where when JavaScript is toggled on/off there is very little visible change on the left aside from the image loading as most of the HTML was available without JavaScript.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/ssr-hydration/create-react-app-toggle-javascript.gif&quot; alt=&quot;gif of enabling/disabling JavaScript for site built with create-react-app&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In contrast, in the above image of a Create-React-App which uses client-side rendering and the browser is responsible for constructing the initial HTML. Due to this we just see the bare-bones HTML as opposed to a full HTML document when JavaScript is disabled.&lt;/p&gt;
&lt;h2&gt;My server-side app looks great in development...What could go wrong? 😅&lt;/h2&gt;
&lt;p&gt;We just looked at an example of Server-Side rendering that looked great in production both with or without JavaScript! What could go wrong? There are some common issues you might run into with Server-Side rendered applications that only occur during the initial hydration process in production such as layout shifts or errors that only appear at build-time.&lt;/p&gt;
&lt;h3&gt;1. Missing Data on Server-Side&lt;/h3&gt;
&lt;p&gt;Something helpful to keep in mind is that some data just is not available in the static server context like user or browser-specific data. For example, window size, authentication status, local storage, etc.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/ssr-hydration/target-nav.gif&quot; alt=&quot;screenshot of target.com navigation&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In the above image of Target&apos;s navigation, you&apos;ll see that the store location data, my name, and items in the shopping cart were not available on the initial page load. Once the data was available it hydrated on the page without shifting the layout. Loading patterns like this can be common on server-side rendered applications.&lt;/p&gt;
&lt;h3&gt;2. Unavailable JavaScript&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/media/ssr-hydration/what-could-go-wrong.gif&quot; alt=&quot;screenshot of my site header loading with disjointed icons and shifting layout&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Let&apos;s debug the above hydration issue that caused my site to have multiple unnecessary rendering changes during load. Something huge that is not available on initial load and can cause issues in server-side rendered applications is JavaScript! It&apos;s considered a best practice to load CSS before JavaScript therefore you need to consider how the HTML and CSS on a page load BEFORE JavaScript is available since JavaScript is not required for the page to load.&lt;/p&gt;
&lt;p&gt;You may end up noticing weird changes on the initial page load that change too quickly to properly inspect - especially if you have a faster internet connection. But there are ways to slow down and really see what is going on. In particular, I&apos;d recommend disabling JavaScript in your browser or using a site like &lt;a href=&quot;https://www.webpagetest.org/&quot;&gt;web page test&lt;/a&gt; to generate film strip thumbnails that show you exactly how the page is loading step by step.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/ssr-hydration/font-awesome-rendering-issue.png&quot; alt=&quot;filmstrips of my site loading with rendering issue&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Above is the waterfall I took of the issue on my site before it was resolved. You can see one of the issues is that the size of the FontAwesome icons changes drastically between 96% and 99% loaded which can be a disjointing experience.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/ssr-hydration/jsstocss.png&quot; alt=&quot;crossed out JavaScript icon transitioning to CSS&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The solution for getting rid of the resizing of the icons during load involved replicating the final styling with local CSS and removing any dependency on FontAwesome&apos;s external CSS which required JavaScript to be available.&lt;/p&gt;
&lt;p&gt;I disabled JavaScript which allowed me to see in development that the ways the icons look before they were fully loaded mirrored the app without JavaScript. This led me to realize Font Awesome was using its own styling that was coming in through JS that conflicted with my local CSS style. Since CSS loads before JS, disabling Font Awesome&apos;s external CSS (loaded via JavaScript) and replicating the CSS styles I wanted locally resolved the issue&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/ssr-hydration/after-fixing-fontawesome-issue.png&quot; alt=&quot;film strip of my site loading without layout  shifts&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You&apos;ll notice after (above image) removing the dependency on Font Awesome&apos;s CSS that the styling of the icons remains consistent as the application loads.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout&amp;gt;
&amp;lt;a href=&quot;/blog/less-javascript-is-more/&quot;&amp;gt;I wrote an article&amp;lt;/a&amp;gt; with more
information about my experience resolving the Font Awesome rendering issues.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h3&gt;3. Immutable Layout&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/media/ssr-hydration/shifting-items.gif#center&quot; alt=&quot;gif of colorful shifting blocks&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The previous issue of changing styles is related to a much larger issue of handling layouts on the server-side. Generally, you should avoid unnecessary layout shifts during page load by implementing layouts with placeholder/gap for expected client-side content and avoiding using JavaScript to position or style content instead of CSS. It is common for some data to be unavailable as the page loads, however, you can develop in a way that can handle missing data by leaving room in the UI for the data to load. In the Target navigation example you can see there is no shift as the user/store specific data is loaded.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/ssr-hydration/target-nav.gif&quot; alt=&quot;screenshot of target navigation&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;4. Strange Conditional Rendering in Server Context&lt;/h3&gt;
&lt;p&gt;If you write React you may have conditionally rendered content like the below code snippet based on screen size using the MatchMedia API. However, this approach might lead to some unnecessary frustration...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (small) {
  return &amp;lt;MobileApp /&amp;gt;;
} else {
  return &amp;lt;DesktopApp /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;matchMedia()&lt;/code&gt; API can&apos;t reliably detect the browser or device size in the server context which can lead to some strange rendering issues as the page loads if the originally set media size doesn&apos;t match the actual browser.&lt;/p&gt;
&lt;p&gt;It&apos;s preferable to use CSS or a library like fresnel which wraps all &lt;code&gt;Media&lt;/code&gt; components in CSS instead of MatchMedia in Server-Side rendered applications to layout content. Since CSS loads before JS, styles applied via CSS, unlike JavaScript should visibly match what you expect on page load.&lt;/p&gt;
&lt;p&gt;Below is an example of how Fresnel can be used. First, you need to import createMedia from Fresnel then define the breakpoints and export MediaContextProvider from the object created from createMedia to wrap the entire app. Then you can use Fresnel&apos;s Media component throughout your app to render components based on the predefined breakpoints.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import ReactDOM from &quot;react-dom&quot;;
import { createMedia } from &quot;@artsy/fresnel&quot;;

const { MediaContextProvider, Media } = createMedia({
  breakpoints: {
    sm: 0,
    md: 768,
  },
});

const App = () =&amp;gt; (
  &amp;lt;MediaContextProvider&amp;gt;
    &amp;lt;Media at=&quot;sm&quot;&amp;gt;
      &amp;lt;MobileApp /&amp;gt;
    &amp;lt;/Media&amp;gt;
    &amp;lt;Media greaterThan=&quot;sm&quot;&amp;gt;
      &amp;lt;DesktopApp /&amp;gt;
    &amp;lt;/Media&amp;gt;
  &amp;lt;/MediaContextProvider&amp;gt;
);

ReactDOM.render(&amp;lt;App /&amp;gt;, document.getElementById(&quot;react&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final step is inject the CSS in the server by passing mediaStyle into a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag in the head of the document so that CSS can be generated from fresnel markup and be rendered on the server. You can read more about setting up Fresnel for SSR in the &lt;a href=&quot;https://github.com/artsy/fresnel#server-side-rendering-ssr-usage&quot;&gt;Fresnel docs&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;5. Error: Window is undefined&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/media/ssr-hydration/missing.gif#center&quot; alt=&quot;gif homer simpson looking under couch cushions looking for something&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you attempt to access browser-specific elements in a server context JavaScript will not be able to resolve those elements.&lt;/p&gt;
&lt;p&gt;When building a site you might run into the &lt;code&gt;window is undefined&lt;/code&gt; or &lt;code&gt;document is undefined&lt;/code&gt; error. This happens when logic within an app assumes the &lt;strong&gt;browser&lt;/strong&gt; window is defined in a &lt;strong&gt;server&lt;/strong&gt; and reference browser-specific elements in the server.&lt;/p&gt;
&lt;p&gt;Your first inclination to resolve the undefined Window error might be to write something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typeof window !== undefined ? //render component : // return null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, if your app is using the ReactDOM.hydrate API to transform the site from HTML to the virtual DOM you need to be aware of ReactDOM.hydrate&apos;s constraint. ReactDOM.hydrate():&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;👯‍♂️ expects that the rendered content is &lt;strong&gt;identical&lt;/strong&gt; between the server and the client.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;🙅🏾‍♀️ does not guarantee that attribute differences will be patched up in case of mismatches.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Hydrate API that converts HTML to full-fledged React &lt;strong&gt;expects that the content is always identical between the server and client&lt;/strong&gt; and does not guarantee that matches will be patched up in case of mismatches. Due to this lack of guarantee, it is NOT a good idea to conditionally render based on elements that will differ between the server and client.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout&amp;gt;
Check out{&quot; &quot;}
&amp;lt;a href=&quot;https://joshwcomeau.com/react/the-perils-of-rehydration/&quot;&amp;gt;
this article
&amp;lt;/a&amp;gt;{&quot; &quot;}
by Josh W Comeau for examples of visual discrepancies that can occur when
React.Hydrate() fails to reconcile an unexpected difference.{&quot; &quot;}
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;Safely accessing browser elements enables you to
avoid reconciliation errors when ReactDOM.hydrates a site from HTML to React. In order to avoid issues with the hydration reconciliation process, you can wrap any side effects that rely on the Window or Document in a &lt;a href=&quot;https://reactjs.org/docs/hooks-effect.html&quot;&gt;useEffect&lt;/a&gt; hook since that only fires after the component has mounted.&lt;/p&gt;
&lt;p&gt;useEffect() Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function Example() {
  const [count, setCount] = state(0);
  useEffect(() =&amp;gt; {
    document.title = `You clicked ${count} times`;
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is an example from the &lt;a href=&quot;https://reactjs.org/docs/hooks-effect.html&quot;&gt;React Docs&lt;/a&gt; of referencing a browser element, &lt;code&gt;document.title&lt;/code&gt; within &lt;code&gt;useEffect()&lt;/code&gt;. This code will never be executed on the server as it executes after the React Virtual DOM is available and therefore avoids running into issues with React.Hydrate().&lt;/p&gt;
&lt;h2&gt;Rule of Least Power&lt;/h2&gt;
&lt;p&gt;With JavaScript comes great responsibility, sometimes JavaScript just isn&apos;t the right tool for the job:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;JavaScript is a powerful language that can do some incredible things, but it’s incredibly easy to jump to using it too early in development when you could be using HTML and CSS instead. Consider &lt;strong&gt;the rule of least power&lt;/strong&gt;: Don’t use the more powerful language (JavaScript) until you’ve exhausted the capabilities of less powerful languages (HTML).&quot; - Iain Bean, Your blog doesn’t need a JavaScript framework&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/media/ssr-hydration/art-direction.gif&quot; alt=&quot;image of my site header dynamically changing headshot images based on the screen size&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I recently used the rule of least power to speed up the initial load time of my header and eliminate relying on JavaScript to dynamically load different headers images on my site based on screen size.&lt;/p&gt;
&lt;p&gt;I was looking into how to display different images based on screen size and stumbled up HTML art direction which can be used to dynamically load images based on the screen size using HTML &lt;code&gt;srcset&lt;/code&gt; attributes instead of JavaScript. Swapping images at different screen sizes can be done with JavaScript or CSS instead of native HTML attributes however using HTML can improve page loading performance as it prevents unnecessarily preloading two images.&lt;/p&gt;
&lt;p&gt;The cool thing about the HTML approach is that it can improve page loading performance as it allows the browser to only preload the image that is visible within the viewport. This can be especially beneficial if you need to display multiple images at various places within a site depending on the screen size.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;picture&amp;gt;
  &amp;lt;source media=&quot;(min-width: 625px)&quot; srcset=&quot;animonica-full.png&quot; /&amp;gt;

  &amp;lt;source srcset=&quot;animonica-headshot-cropped.png&quot; /&amp;gt;

  &amp;lt;img src=&quot;animonica-full.png&quot; alt=&quot;Illustrated Monica&quot; /&amp;gt;
&amp;lt;/picture&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to set up this functionality in HTML you can use the picture attribute and set media queries on each source image. It will return the first condition the is true and as a fall back it&apos;ll return the image from the img tag.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout&amp;gt;
It&apos;s also possible to use the art-direction API in Gatsby with gatsby-image
which is Gatsby&apos;s package for loading and transforming images from GraphQL. I
wrote
&amp;lt;a href=&quot;/blog/2020-06-24-exploring-art-direction-in-gatsby/&quot;&amp;gt;an article&amp;lt;/a&amp;gt;
with more information about my epxloration into using art direction with
Gatsby.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;In a Server-Side Rendered context it&apos;s important to consider &lt;em&gt;how&lt;/em&gt; the page loads both when data is and is not available.&lt;/li&gt;
&lt;li&gt;CSS is the right tool for handling layout, especially in a Server-Side Rendered Application. Using JavaScript for styling in SSR apps can lead to strange loading experiences for some users.&lt;/li&gt;
&lt;li&gt;It&apos;s important to guard references to browser-specific elements like &lt;code&gt;document&lt;/code&gt; or &lt;code&gt;window&lt;/code&gt; within &lt;code&gt;useEffect()&lt;/code&gt; to avoid reconciliation error as the page hydrates to transform SSR apps from HTML to React.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Resources &amp;amp; Further Reading&lt;/h3&gt;
&lt;p&gt;Below are some resources I recommend if you&apos;re looking to further explore the rendering process for server-side rendered React applications.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;https://nextjs.org/docs/basic-features/pages#server-side-rendering&lt;/li&gt;
&lt;li&gt;https://reactjs.org/docs/reconciliation.html&lt;/li&gt;
&lt;li&gt;https://www.gatsbyjs.org/docs/react-hydration/&lt;/li&gt;
&lt;li&gt;https://joshwcomeau.com/react/the-perils-of-rehydration/&lt;/li&gt;
&lt;li&gt;https://www.webpagetest.org/&lt;/li&gt;
&lt;li&gt;https://github.com/artsy/fresnel&lt;/li&gt;
&lt;li&gt;https://www.speedpatterns.com/patterns/immutable_layout.html&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Set Yourself Up For Success During Hacktoberfest</title><link>https://aboutmonica.com/blog/set-yourself-up-for-success-open-source-contributions/</link><guid isPermaLink="true">https://aboutmonica.com/blog/set-yourself-up-for-success-open-source-contributions/</guid><description>This article walks through some tips for making meaningful contributions to Open Source software during Hacktoberfest and beyond.</description><pubDate>Thu, 01 Oct 2020 13:08:03 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;Throughout &lt;a href=&quot;https://hacktoberfest.digitalocean.com/&quot;&gt;Hacktoberfest&lt;/a&gt; (October) people are encouraged to contribute to open-source to secure a t-shirt along with other swag. I have long been a fan of Hacktoberfest as some of my initial contributions to open-source software occurred during Hacktoberfest. Although, a downside to Hacktoberfest for some project maintainers is that people are encouraged to submit low-quality, spammy pull requests which directly increases the time required for them to maintain their projects without the benefit of being able to incorporate the code that was submitted [Update: &lt;a href=&quot;https://hacktoberfest.digitalocean.com/hacktoberfest-update&quot;&gt;As of 10/03/2020 project maintainers must opt-in to participating in Hacktoberfest&lt;/a&gt;]. A lot of project maintainers for projects without large corporate funding are unpaid (or underpaid) volunteers.&lt;/p&gt;
&lt;p&gt;As I&apos;ve contributed to larger open-source projects and maintained smaller projects over the years I&apos;ve learned more about what types of open-source contributions are generally accepted. I recently read through &lt;a href=&quot;https://press.stripe.com/#working-in-public&quot;&gt;Working in Public&lt;/a&gt; by Nadia Eghbal (and &lt;a href=&quot;/blog/working-in-public-notes/&quot;&gt;shared my notes&lt;/a&gt;) which is a great primer if you are interested in learning more about the labor that goes into maintaining open-source software and norms for participating in various types of open-source communities. Below are some of my thoughts on how to make quality contributions to open-source during Hacktoberfest and beyond! I plan on updating this post over time.&lt;/p&gt;
&lt;h2&gt;1. Finding Issues&lt;/h2&gt;
&lt;p&gt;If you&apos;re newer to contributing and are looking for issues that are designed to be smaller in scope, instead of opening pull requests with unsolicited, 1 character changes, I&apos;d recommend using GitHub&apos;s issues filter to &lt;a href=&quot;https://github.com/issues?q=label%3A%22good+first+issue%22+label%3Ahacktoberfest+&quot;&gt;search for &quot;Hacktoberfest&quot; or &quot;Good First Issue&quot;&lt;/a&gt; or checking out these sites that help people find beginner-level issues that you&apos;re interested in contributing to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;https://goodfirstissue.dev/&lt;/li&gt;
&lt;li&gt;https://www.firsttimersonly.com/&lt;/li&gt;
&lt;li&gt;https://github.com/MunGell/awesome-for-beginners&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The benefit of spending the time to find issues that are well-scoped to your interest and experience level is that if you make an attempt at resolving an issue based on the requirements usually maintainers are more willing to work with you to help get your changes accepted into a project.&lt;/p&gt;
&lt;p&gt;In addition to using the above resources to find good first issues, I&apos;d also recommend contributing to projects you already use or check out the following projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/wesbos/awesome-uses/&quot;&gt;awesome-uses&lt;/a&gt; • share your developer setup, gear, software and configs.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/M0nica/httriri&quot;&gt;httriri: HTTRiRi - HTTP Status Codes as Portrayed by Rihanna GIFs ✨💄&lt;/a&gt; • submit your favorite Rihanna GIF that accurately potrays a status code and improve an educational resource for those looking for an HTTP status code reference.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/twilio-labs/open-pixel-art&quot;&gt;twilio-labs/open-pixel-art&lt;/a&gt; • Contribute to a fun, collaborative pixel art project to teach people how to contribute to open-source.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/selfdefined/web-app/issues&quot;&gt;selfdefined/web-app: Dictionary database with future API and bot integrations&lt;/a&gt; • submit definitions and enhancements to the selfdefined dictionary.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;danger&quot;&amp;gt;
Hacktoberfest has been updated to require maintainers to opt-in to having
contributions to their projects count towards Hacktoberfest. If you would like
your contributions to count towards the Hacktoberfest requirements, explore
the{&quot; &quot;}
&amp;lt;a href=&quot;https://github.com/topics/hacktoberfest&quot;&amp;gt;
Hacktoberfest topic on GitHub
&amp;lt;/a&amp;gt;
to find projects that have explicitly opted-in to participating.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h2&gt;2. Claiming Issues&lt;/h2&gt;
&lt;p&gt;Different projects have different norms for indicating that someone is planning on working on an issue. As a starter, I would recommend commenting on any issues that you are interested in working on to reduce the chances of multiple people submitting duplicate changes to resolve the same issues. Generally, unless a maintainer already had someone in mind to work on an issue whoever &quot;claims&quot; an issue first and submits a pull request first will be prioritized and later submissions may be viewed as spammy. Also, note that not all issues are opened by project maintainers and core contributors so it&apos;s helpful to read the conversation and tags on an issue to determine if a maintainer is interested in resolving the issues that were raised in the issue.&lt;/p&gt;
&lt;h2&gt;3. Read the Documentation&lt;/h2&gt;
&lt;p&gt;I would recommend focusing on contributing to projects that have at least a basic README that outlines how to get started with contributing and what the expectations are for contributors. Another file that some repositories have to help provide more context for contributors is a
CONTRIBUTING.md which indicates what the expectations are for how people should contribute to a project. For example, for my project https://www.httriri.com/
I&apos;ve shared guidelines for how files should be named and how to make changes that are more likely to be accepted (i.e, not submitting duplicate content, checking open issues before submitting unsolicited changes) in the README file to help make it easier for new contributors to get new status code submissions accepted:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The GIF should include Rihanna and not already be included on https://www.httriri.com/. In rare cases, a GIF that already exists in the HTTRIRI collection can be used for another status code but within the same set of proposed changes, a new GIF should be chosen for the other status code to ensure there are no duplicate images and the overall size of the collection is growing. Excerpt from &lt;a href=&quot;https://github.com/M0nica/httriri&quot;&gt;HTTRIRI Documentation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If there are things specified within a repository regarding naming conventions, testing practices, where files should live, etc. these are usually requirements and not suggestions unless otherwise indicated. Therefore, it&apos;s in your best interest to follow those guidelines to hopefully have your changes accepted and to reduce the back and forth required to finalize your pull request.&lt;/p&gt;
&lt;h2&gt;4. Opening a Pull request&lt;/h2&gt;
&lt;p&gt;Once you feel like your changes adequately address an issue you should open a pull request in the repository. It&apos;s generally a best practice to reference the issue that you are resolving in your pull request and to add a link to your PR on the issue. Some projects will include a checklist or template when opening a new pull request, this generally should be filled out completely unless something is not applicable and gives you another chance to check that your contribution meets the requirements. For example, if a checklist indicates that unit tests should be updated or added for all changes then you should make sure that is done in your pull request and if not indicate why you have decided to forgo a particular requirement.&lt;/p&gt;
&lt;p&gt;If you&apos;re not quite ready to submit a PR for feedback but need assistance I&apos;d recommend &lt;a href=&quot;https://github.blog/2019-02-14-introducing-draft-pull-requests/&quot;&gt;opening a draft PR&lt;/a&gt; or sharing a link to your branch on the issue and requesting guidance from other contributors. Depending on the project they may indicate that other forums like Discord should be used to solicit help with contributing. Not all projects have the same capacity to help newer contributors, so you may have to ask for help from others that aren&apos;t necessarily directly involved in the project. For example, if you&apos;re contributing to a popular, React repository a project maintainer may not be able to answer a lot of follow-up questions but someone in a popular React Discord might be able to help you.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Overall, to increase the chance of your pull request being accepted I would recommend first identifying an open issue that a maintainer has already given the green light to be worked on, indicating that you are interested in that issue if it&apos;s still up for grabs and hasn&apos;t been claimed, reading through the project documentation and guidelines to make sure your contribution is aligned with their expectations for contributions and opening a pull request once you&apos;ve completed the previous steps. I hope these tips help you get closer to successfully contributing to open-source software!&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Setting Up Shell with zsh and Autosuggestions</title><link>https://aboutmonica.com/blog/setting-up-zsh-with-autosuggestions/</link><guid isPermaLink="true">https://aboutmonica.com/blog/setting-up-zsh-with-autosuggestions/</guid><description>This post is an overview of my recent migration from bash to zsh.</description><pubDate>Fri, 14 May 2021 13:12:21 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;This post is an overview of how I recently migrated my bash history and settings from Bash to zsh and set up autosuggestions for my terminal. For a while whenever I opened a new terminal window,the below message greeted me to prompt me to migrate to zsh, which is the default shell for newer macOS versions (as of macOS Catalina):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;              *
              *
            * | *
      * * * \|O|/ * * *
       \o\o\o|O|o/o/o/
       (&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;O&amp;lt;&amp;gt;&amp;lt;&amp;gt;&amp;lt;&amp;gt;)
        &apos;===========&apos;

The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Migrating to zsh while carrying over some of your bash settings is a bit more involved then just running &lt;code&gt;chsh -s /bin/zsh&lt;/code&gt;. When I was migrating to zsh I knew that I wanted to be able to use autosuggestions out of the box to auto-populate and enable more robust re-use of commands than setting up aliases. Aliases involve mapping a shorter or more memorable command to another.&lt;/p&gt;
&lt;h2&gt;Set zsh as default for macOS account&lt;/h2&gt;
&lt;p&gt;Update macOS account to use zsh as the default by running &lt;code&gt;chsh -s /bin/zsh&lt;/code&gt;. After running this command your terminal &lt;em&gt;should&lt;/em&gt; look different. For me , after running this command then when I opened a new terminal window my terminal looked different as I prior to converting to zsh I had the &lt;a href=&quot;https://github.com/minamarkham/yonce&quot;&gt;Yoncé Bash It&lt;/a&gt; theme set up which zsh doesn&apos;t support.&lt;/p&gt;
&lt;h2&gt;Set zsh as the default for VSCode&lt;/h2&gt;
&lt;p&gt;If you use VSCode or another IDE with an integrate terminal then you may need to update it to use zsh as the default for the integrated terminal or else it will default to the previous default.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open VSCode&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;SHIFT + COMMAND + P&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;type in &quot;Terminal&quot; and select &quot;Terminal: Select Default Profile&quot; from the dropdown&lt;/li&gt;
&lt;li&gt;select zsh&lt;/li&gt;
&lt;li&gt;new terminals opened in VSCode should now use zsh.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/media/selectdefaultprofileinvscode.gif&quot; alt=&quot;gif of selecting default profile in VSCode&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Migrate information from bash to zsh&lt;/h2&gt;
&lt;h3&gt;Copy bash history over to zsh&lt;/h3&gt;
&lt;p&gt;It is helpful to migrate over some of the contents from &lt;code&gt;~/.bash_history&lt;/code&gt; in order to better take advantage of zsh&apos;s autocomplete features shortly after migrating. Bash has a file &lt;code&gt;~/.bash_history&lt;/code&gt; that stores previously run commands and zsh has &lt;code&gt;~/.zsh_history&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout&amp;gt;
I copied 1000 of the most recent commands (a.k.a 1000 lines) from my
~/.bash_history to ~/.zsh_history. But you can adjust this number to
accommodate the amount of history that you&apos;re interested in migrating over.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h3&gt;Copy environment variables from Bash to zsh&lt;/h3&gt;
&lt;p&gt;zsh should be aware of any relevant env variables that are in &lt;code&gt;~./bash_profile&lt;/code&gt;. Env variables in zsh live in &lt;code&gt;~./zshenv&lt;/code&gt;. Depending on what&apos;s contained in your &lt;code&gt;~./bash_profile&lt;/code&gt; some items may be better suited to be placed in other &lt;code&gt;zsh&lt;/code&gt; files like &lt;code&gt;~/.zshrc&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Install a zsh plugin manager&lt;/h2&gt;
&lt;p&gt;Software like &lt;a href=&quot;https://ohmyz.sh&quot;&gt;oh-my-zsh&lt;/a&gt; can help enable more robust zsh configurations than the default zsh. You can install oh-my zsh with the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run the following to install ohmyzsh on your computer.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sh -c &quot;$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Visit https://ohmyz.sh/ for the latest installation instructions.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;After successfully installing oh my zsh you may see the following confirmation in your terminal&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;Cloning Oh My Zsh...
Cloning into &apos;/Users/monica/.oh-my-zsh&apos;...
remote: Enumerating objects: 1210, done.
remote: Counting objects: 100% (1210/1210), done.
remote: Compressing objects: 100% (1175/1175), done.
remote: Total 1210 (delta 20), reused 1096 (delta 15), pack-reused 0
Receiving objects: 100% (1210/1210), 843.85 KiB | 9.48 MiB/s, done.
Resolving deltas: 100% (20/20), done.

Looking for an existing zsh config...
Found ~/.zshrc. Backing up to /Users/monica/.zshrc.pre-oh-my-zsh
Using the Oh My Zsh template file and adding it to ~/.zshrc.

         __                                     __
  ____  / /_     ____ ___  __  __   ____  _____/ /_
 / __ \/ __ \   / __ `__ \/ / / /  /_  / / ___/ __ \
/ /_/ / / / /  / / / / / / /_/ /    / /_(__  ) / / /
\____/_/ /_/  /_/ /_/ /_/\__, /    /___/____/_/ /_/
                        /____/                       ....is now installed!


Before you scream Oh My Zsh! please look over the ~/.zshrc file to select plugins, themes, and options.

• Follow us on Twitter: https://twitter.com/ohmyzsh
• Join our Discord server: https://discord.gg/ohmyzsh
• Get stickers, shirts, coffee mugs and other swag: https://shop.planetargon.com/collections/oh-my-zsh
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Check out the docs https://github.com/ohmyzsh/ohmyzsh/wiki to view some of the available themes and plugins.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Setup zsh-autosuggestions&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/zsh-users/zsh-autosuggestions&quot;&gt;zsh-autosuggestions&lt;/a&gt; is a plugin that add nice autocomplete functionality to zsh. Below are instructions for installing it as a plugin for oh-my-zsh to use.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Clone Oh My Zsh repository into $ZSH_CUSTOM/plugins
&lt;code&gt;git clone https://github.com/zsh-users/zsh-autosuggestions \${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;~/.zshrc&lt;/code&gt; and add the plugin to the list of plugins for Oh My Zsh to load inside of ~/.zshrc.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;Callout&amp;gt;
{&quot; &quot;}
My plugins in ~/.zshrc looked like &quot;plugins=(git)&quot; when I opened the file so I
updated to add zsh-autosuggestions like &quot;plugins=(git
zsh-autosuggestions)&quot;`.{&quot; &quot;}
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start a new terminal session and commands should now autocomplete based off of the history in ~./zsh_history` (which is added to every time you type a new command)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/media/zshautosuggestions.gif&quot; alt=&quot;gif of the final CLI created by this tutorial&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I am planning on cleaning up my &lt;code&gt;~./zsh_history&lt;/code&gt; a bit if I run into previous commands that had typos or misconfigure paths in order to ensure that autosuggestions isn&apos;t recommending my previous mistakes.&lt;/p&gt;
&lt;p&gt;I am looking forward to learning more about the zsh ecosystem as so far I am enjoying the autosuggestions plugin and I&apos;ve heard that the ecosystem has some nice functionality including how it recursively search files, spell checking and robust themes and plugin support.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Initial Thoughts On Migrating from gatsby-transformer-remark to gatsby-plugin-mdx</title><link>https://aboutmonica.com/blog/thoughts-on-migrating-from-markdown-to-mdx/</link><guid isPermaLink="true">https://aboutmonica.com/blog/thoughts-on-migrating-from-markdown-to-mdx/</guid><description>Some of my initial thoughts from migrating this Gatsby site from gatsby-transformer-remark to gatsby-plugin-mdx in order to support MDX in addition to Markdown.</description><pubDate>Tue, 26 May 2020 11:28:31 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;Yesterday, I decided tackle two things at once, live-streaming for the first time on &lt;a href=&quot;https://www.twitch.tv/blacktechdiva&quot;&gt;Twitch&lt;/a&gt; 🎉 while updating this site to support MDX in addition to Markdown. &lt;a href=&quot;https://github.com/mdx-js/mdx&quot;&gt;MDX&lt;/a&gt; allows developers to write JSX in Markdown documents. I was interested in upgrading this blog to support MDX because from what I&apos;ve seen it will enable me to produce more interactive and engaging blog content.&lt;/p&gt;
&lt;p&gt;Move the slider below to navigate through my MDX journey so far and see MDX in action:&lt;/p&gt;
&lt;p&gt;import Slider from &quot;../../components/SliderExample.jsx&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Slider client:idle /&amp;gt;&lt;/p&gt;
&lt;p&gt;I adapted this slider from the one in this article on &lt;a href=&quot;https://johno.com/year-of-the-interactive-blog-post/&quot;&gt;The Year of The Interactive Blog Post&lt;/a&gt;
which features various examples of articles with immersive and engaging interactive experiences.&lt;/p&gt;
&lt;p&gt;Here are some of my initial thoughts and findings from migrating this Gatsby site from using Markdown to MDX&lt;/p&gt;
&lt;h2&gt;Beware of Conflicting Plugins&lt;/h2&gt;
&lt;p&gt;While I was migrating from gatsby-transformer-remark to gatsby-plugin-mdx to allow my site to support .MDX files in addition to .MD files I followed the steps in this Gastby article on &lt;a href=&quot;https://www.gatsbyjs.org/blog/2019-11-21-how-to-convert-an-existing-gatsby-blog-to-use-mdx/&quot;&gt;converting an existing Gatsby blog to use MDX&lt;/a&gt;. However, along the way I ran into an error accessing some of the fields that should be available for mdx nodes like &lt;code&gt;body&lt;/code&gt;,&lt;code&gt;timeToRead&lt;/code&gt; and &lt;code&gt;excerpt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;danger&quot;&amp;gt;
{&quot; &quot;}
error Cannot query field &quot;body&quot; on type &quot;Mdx&quot;. Did you mean &quot;rawBody&quot;?
graphql/template-strings
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;While debugging the issue I ended up commenting out all of the plugins in &lt;code&gt;gatsbyRemarkPlugins&lt;/code&gt; and confirming that all of the data that should be available to query from mdx was available. I then re-added each plugin one-by-one to determine the culprit. In my case there was a conflict with gatsby-plugin-mdx and the plugin I use to generate Open Graph images for all of my posts (gatsby-plugin-my-social-cards). Until I replaced gatsby-plugin-my-social-cards with gatsby-remark-twitter-cards the plugin &lt;code&gt;body&lt;/code&gt;,&lt;code&gt;timeToRead&lt;/code&gt; and &lt;code&gt;excerpt&lt;/code&gt; were not available to query.&lt;/p&gt;
&lt;p&gt;I am not sure how common plugin conflicts like this are but it&apos;s definitely something to be aware of when migrating from gatsby-transformer-remark to gatsby-plugin-mdx.&lt;/p&gt;
&lt;h2&gt;Upgrade Your Syntax Highlighting&lt;/h2&gt;
&lt;p&gt;So far I am impressed with the developer experience of being able to use React components within MDX. However, in order to have better syntax highlighting for the AST of MDX files I needed to install an MDX specific plugin, &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=JounQin.vscode-mdx&quot;&gt;VSCode MDX Plugin&lt;/a&gt;. The syntax highlighting with this plugin is signficantly better than without but it does seem a bit &lt;em&gt;off&lt;/em&gt;. I am curious if there&apos;s a better syntax highlighting option for MDX.&lt;/p&gt;
&lt;h2&gt;Create Reusable Components for Repetitive Markdown&lt;/h2&gt;
&lt;p&gt;Aside from being able to support more interactivity in my writing I most excited about using MDX to be able to isolate certain functionality into distinct components. After migrating to MDX I was able to replace my ad-hoc Markdown based Table of Contents with a React component&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/media/toc-diff.png&quot; alt=&quot;diff with markdown table of contents vs MDX table of contents&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The above screenshot of the code diff shows how I was able to replace the multiple lines of Markdown that were previously required for me to generate a Table of Contents with a one-line component that is able to parse the tableOfContents from the mdx nodes.&lt;/p&gt;
&lt;p&gt;This article is very much a garden post 🌱 as I wanted to document my initial learnings and thoughts from updating my site to support mdx.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Tips for Debugging Software like a Detective</title><link>https://aboutmonica.com/blog/tips-for-debugging-software-like-a-detective/</link><guid isPermaLink="true">https://aboutmonica.com/blog/tips-for-debugging-software-like-a-detective/</guid><description>Strategic ways engineers can approach software with detective-like debugging skills</description><pubDate>Sat, 15 May 2021 15:35:06 GMT</pubDate><content:encoded>&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;
import { Tweet } from &quot;@astro-community/astro-embed-twitter&quot;;&lt;/p&gt;
&lt;p&gt;Being an effective web developer when investigating a software issue, locally or in production, requires some detective-like debugging skills. As I&apos;ve grown as a developer I&apos;ve not only improved the breadth of my knowledge about various ways software can break in unexpected ways but I&apos;ve also enhanced my debugging skills over time.&lt;/p&gt;
&lt;p&gt;&amp;lt;Tweet id=&quot;https://twitter.com/indigitalcolor/status/1392464056011866114?s&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;In this article, I share a longer-form version of the above Twitter thread which highlights some of my thoughts on various ways to approach understanding why the computer doesn&apos;t appear to be doing what you tell it to do. Computers interpret instructions literally and to resolve a discrepancy between actual and expected functionality you may need to revisit your assumptions!&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout&amp;gt;
{&quot; &quot;}
Software engineers can hold a lot of ill-informed assumptions or falsehoods
which can lead to subpar handling of &quot;edge cases&quot; like assuming a user&apos;s name
is permanent or that a name must by X characters or that a name is only
structured as first name, middle name, last name when in reality that is not a
universal fact. Check out{&quot; &quot;}
&amp;lt;a href=&quot;https://github.com/kdeldycke/awesome-falsehood&quot;&amp;gt;
awesome-falsehood
&amp;lt;/a&amp;gt;{&quot; &quot;}
if you&apos;d like to learn more about common falsehoods that engineers believe
about things like time, names, addresses, and more.{&quot; &quot;}
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;If an issue seems to be isolated to my local environment and has gone from working to not working without any notable software changes I may try &quot;turning it on and off again&quot; by trying some of the following: rebuilding database, doing a fresh install of node_modules, restarting docker or your computer, etc. But what do you do when just turning it on and off again doesn’t resolve the issue?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Run linting and read any errors&lt;/strong&gt; Is there a blaring syntax error? Is your code referencing an undefined variable? More times than I care to admit my code wasn&apos;t compiling locally because there was a rouge letter &apos;f&apos; that I somehow added to a random line. This misplaced char is often picked up by my linting but if I do want to manually skim my changes since the app successfully compiled then I will use the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens&quot;&gt;VSCode Git Lens Integration&lt;/a&gt; to see if anything stands out as &lt;em&gt;off&lt;/em&gt; with my changes. Similar to linting errors, if there are other errors in your terminal, browser console, or webpage that are appearing when your app fails to compile or under the unexpected condition they can help guide your search to figure out what is going wrong. When I first was learning how to program, I more so stared 😳 at error messages instead of reading them. If you&apos;re still getting comfortable with error messages check out &lt;a href=&quot;https://nickymeuleman.netlify.app/blog/love-errors&quot;&gt;Nicky Meuleman tips on learning to appreciate errors&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Only change one thing at a time&lt;/strong&gt;. It can be tempting to change everything at once. But try just changing one thing and before you change it think through how that one change will help you confirm or reject your current assumptions of the problem and ultimately help you further your understanding and bring you closer to a potential solution. Automated tests can be helpful to confirm how changes work under different test cases and to ensure as you progress through the problem you don&apos;t unknowingly break preexisting test cases that were previously passing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Confirm all of your assumptions.&lt;/strong&gt; Consistently reproduce the issue manually or with tests. Under what conditions does the system fail? To better understand an underlying issue you should determine what conditions it behaves unexpectedly in. Using logs, breakpoints, tests, check network calls, etc to sort out when it fails will get you closer to understanding why it&apos;s not working as expected. Is your API actually returning the data you expect? Is it an environment-specific issue? Are there missing environment variables?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lean on tests to automate confirming your assumptions and testing potential solutions&lt;/strong&gt; Approaching development with a red-green refactor development approach of defining desired functionality in test, implementing code to make the test go from red to green, and then refactoring without having to worry about unknowingly breaking the desired functionality. This approach prevents building out too much of a feature without testing its functionality and if bugs are resolved in a test-driven way then that means that if a similar regression is made in the future in the codebase that there will be a clear failing test warning future developers that something is amiss. Git also has command, bisect which can be used to quickly find a problematic commit, by strategically checking out specific commits from the git history and then based on whether or not that commit is identified as being before or after the software regression was introduced then the next commit is either earlier or later in the git history until the culprit can be identified. Git bisect can be combined with automated testing to make the process of identifying the problematic commit even more efficient.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Walk through your code line by line&lt;/strong&gt;. Try to read it with fresh eyes even if that means taking a 5 min break and revisiting. I&apos;m always amazed how sleeping on a problem that stumped me usually makes a lot more sense in the morning. It&apos;s definitely possible for your mind to continue working through a problem even when you are not directly in front of a screen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Look at the source code of the third-party package.&lt;/strong&gt; check for open issues. If the error may be related to third-party software look at its source code. I’ve looked at React, misc. packages with types, Webmention, etc on GitHub to better understand their functionality and find relevant open issues. Oftentimes others may have encountered a similar issue.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Google is your best friend&lt;/strong&gt; A quick search can be a developer’s best friend. &lt;strong&gt;Are you using a new function or package? Can you find an example where it&apos;s working?&lt;/strong&gt; If you want to see code that uses the same APIs in context then recommend searching your local repo or https://grep.app to search open source repos.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Phone a friend.&lt;/strong&gt; I recommend integrating git blame into your development environment I use the &lt;a href=&quot;https://gitlens.amod.io/&quot;&gt;Git lens&lt;/a&gt; plugin which shows you, directly within files in a code editor, who authored certain file changes and the PR which can be helpful for quickly getting more context regarding decisions by looking at the associated Pull Request or having the opportunity to connect directly with the committer. Pair programming can be an effective way to debug and share knowledge. Similar, to the magic of figuring out the solution to a bug in the shower or overnight there is a phenomenon called rubber ducky debugging in which just explaining the issue to someone else (even an inanimate rubber duck) can help make the solution more obvious.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once you’ve sorted out a gnarly or even an &quot;obvious&quot; in hindsight bug remember to document your learnings. Even if it’s just a quick note for your future self.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>6 Transformative Tech Conference Talks</title><link>https://aboutmonica.com/blog/transformative-conference-talks/</link><guid isPermaLink="true">https://aboutmonica.com/blog/transformative-conference-talks/</guid><description>Here&apos;s a roundup of some of my favorite conference talks that have shaped the way that I think about web development, the workplace, and community organizing.</description><pubDate>Sat, 22 Aug 2020 11:59:17 GMT</pubDate><content:encoded>&lt;p&gt;Here&apos;s a round-up of some of my favorite conference talks that have shaped the way that I think about web technologies, the workplace and community organizing. I hope some of these talks will also cause you to reflect and in some ways become a better technologist.&lt;/p&gt;
&lt;h2&gt;1. Glue Talk by Tanya Reilly&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe
allowfullscreen=&quot;&quot;
frameborder=&quot;0&quot;
height=&quot;315&quot;
src=&quot;https://www.youtube.com/embed/KClAPipnKqw&quot;
width=&quot;500&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;lt;/iframe&amp;gt;
&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I had the pleasure of hearing Tanya Reilly give this talk at a Write/Speak/Code event in NYC. She walks through how to approach &quot;the less glamorous - and often less-promotable - work that needs to happen to make a team successful&quot; through a strategic lens. Often the burden of performing less-promotable work falls on the shoulders of marginalized folks within technology. Her talk is an actionable guide to navigating conversations with your manager to ensure you&apos;re contributing to the type of projects that will be recognized and reduce the chance of any surprises during formal reviews. I (unfortunately) have had to recommend this talk to a lot of women in technology who have experienced being delegated to do a significant share of glue work.
You can check out the slides and sketch notes on Tanya&apos;s site:
&lt;a href=&quot;https://noidea.dog/glue&quot;&gt;https://noidea.dog/glue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;h2&gt;2. JavaScript is AsynchroWAT? By Crystal Martin&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe
allowfullscreen=&quot;&quot;
frameborder=&quot;0&quot;
height=&quot;315&quot;
src=&quot;https://www.youtube.com/embed/l9fhSbby5E&quot;
width=&quot;500&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;lt;/iframe&amp;gt;
&amp;lt;br /&amp;gt;
Crystal Martin breaks down how asynchronous JavaScript works. I feel like this
is a topic that a lot of folks new to JavaScript may have difficulty
understanding and Crystal assures folks that don&apos;t yet fully understand
asynchronous JavaScript that they&apos;re not alone. I found her talk very relatable
and informative. I especially appreciated her bring in examples of an intense
(hair) wash day to describe how JavaScript functions work.
&amp;lt;br /&amp;gt;
&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;3. How to Build a Magical Living Room by Saron Yitbarek&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe
allowfullscreen=&quot;&quot;
frameborder=&quot;0&quot;
height=&quot;315&quot;
src=&quot;https://www.youtube.com/embed/nOscsODuol4&quot;
width=&quot;500&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Saron Yitbarek, the founder of &lt;a href=&quot;https://www.codenewbie.org/&quot;&gt;CodeNewbie&lt;/a&gt; is not only a prolific podcaster but also a phenomenal community organizer and one of my heroes! Her keynote at RubyConf is a master class in building inclusive communities. Saron shares what she&apos;s learned about building a magical living room to make people feel like they&apos;re welcome in your community. You&apos;ll walk away from this talk with actionable advice for becoming a stronger community leader.&lt;/p&gt;
&lt;p&gt;&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;h2&gt;4. Tautology and Business Value Presented by Heidi Waterhouse&lt;/h2&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://yougotthis.io/talks/tautology-business-value/&quot;&amp;gt;
&amp;lt;img src=&quot;/media/tigger-talk.png&quot; /&amp;gt;
&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;Heidi presented this talk at You Got This 2020: From Home. I was able to see a live version at Adulting.dev. Heidi talks through how to strategically understand your value within a business context. I think this talk is especially crucial for folks who have not worked through a recession and/or need additional resources and tips to advocate for themselves in the workplace.&lt;/p&gt;
&lt;h2&gt;5. Building a Custom React Renderer by Sophie Alpert&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe
allowfullscreen=&quot;&quot;
frameborder=&quot;0&quot;
height=&quot;315&quot;
src=&quot;https://www.youtube.com/embed/CGpMlWVcHok&quot;
width=&quot;500&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the coolest talks I&apos;ve seen live was Sophie Alpert walking through how to use the npm package &lt;a href=&quot;https://www.npmjs.com/package/react-reconciler&quot;&gt;react-reconciler&lt;/a&gt; to you build your own React renderer. It inspired me to one day (far away) attempt to give a technical talk with just a text editor. I felt that I had a better understanding of the React reconciliation process after listening to her talk.&lt;/p&gt;
&lt;p&gt;&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;h2&gt;6. Let’s Program Like It’s 1999 by Lee Byron&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe
allowfullscreen=&quot;&quot;
frameborder=&quot;0&quot;
height=&quot;315&quot;
src=&quot;https://www.youtube.com/embed/vG8WpLr6y_U&quot;
width=&quot;500&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;lt;/iframe&amp;gt;
Lee Bryon also gave a phenomenal talk at React Conf where afterward I felt like
I traveled a bit through time. He provided an overview of how much the web has
&lt;em&gt;not&lt;/em&gt; changed over the last 20 years. As a JavaScript developer who has only
dabbled in PHP, I found it especially fascinating to learn how many of the PHP
and LAMP Stack abstractions and mental models have found their way into the
modern JavaScript development ecosystem. More often than not developers discuss
how rapidly web development changes so it was helpful for Lee to share some of
the principles that have, so far, stood the test of time (a.k.a the past 30
years) through various technical implementations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What conference talk has most inspired you? What&apos;s the most interesting presentation you&apos;ve seen so far? Share your favorite conference talks with me on Twitter: &lt;a href=&quot;https://twitter.com/indigitalcolor&quot;&gt;@indigitalcolor&lt;/a&gt;.&lt;/p&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item><item><title>Working In Public Book Notes</title><link>https://aboutmonica.com/blog/working-in-public-notes/</link><guid isPermaLink="true">https://aboutmonica.com/blog/working-in-public-notes/</guid><description>Notes for the book Working In Public by Nadia Eghbal</description><pubDate>Sun, 09 Aug 2020 19:41:03 GMT</pubDate><content:encoded>&lt;p&gt;import { Tweet } from &quot;@astro-community/astro-embed-twitter&quot;;&lt;/p&gt;
&lt;p&gt;import Callout from &quot;../../components/Callout.astro&quot;;&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;garden&quot;&amp;gt;
This reflection was written as I was reading &quot;Working in Public&quot;. Below are my
thoughts/notes from the reading along with some direct quotes.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;I recently picked up a copy of the book &lt;a href=&quot;https://press.stripe.com/#working-in-public&quot;&gt;Working In
Public&lt;/a&gt; by Nadia Eghbal. I was especially interested in reading this book as I recently became a &lt;a href=&quot;https://stars.github.com/&quot;&gt;GitHub Star&lt;/a&gt; and am currently working full-time on open-source work for the first time.&lt;/p&gt;
&lt;p&gt;&amp;lt;Tweet id=&quot;https://twitter.com/waterproofheart/status/1286329713733054464?s&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;I am hoping to walk away from this book with a better understanding of how to help folks newer to open-source who are interested in making meaningful contributions to open-source as well as, what does (or could) sustainable open-source look like.&lt;/p&gt;
&lt;p&gt;In the introduction of Working in Public, Eghbal discusses the tension between initiatives to help more developers contribute to open source and the fact that often the responsibility of working with new contributors is delegated to a few people (maintainers) who might feel overwhelmed by an influx of casual contributors. Casual meaning they only make one-off contributions and might even disappear before submitting their first contribution!&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;book&quot;&amp;gt;
&quot;In my conversations with maintainers, I heard them express a genuine conflict
with wanting to encourage newcomers to participate in open source and feeling
unable to personally take on that work.&quot; (Page 9, Working in Public)
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;Knowing that maintaining takes time, energy and is often volunteer-based leaves me wanting to further explore how can I help people make meaningful contributions to open-source? I believe that for most projects that welcome contributions, a high-quality contribution outweighs the cost of maintenance, but I also am not the maintainer of an open-source project that gets a significant amount of casual contributors.&lt;/p&gt;
&lt;p&gt;I&apos;ve viewed open-source as a community effort but the most popular open-source projects are often predominately the resulting work of a few active contributors or maintainers. This lopsided distribution of work within open-source is evident in contributor graphs and Eghbal points out that 73% of commits to the popular design framework, Bootstrap can be attributed to three developers (Working in Public, Page 10). The amount of people that benefit from a particular open-source can far outweigh the amount of contributors.&lt;/p&gt;
&lt;h2&gt;GitHub as a Platform&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/&quot;&gt;GitHub&lt;/a&gt; has risen as the predominate platform for sharing open-source code, although there are some notable projects that have decided to not use GitHub (for example &lt;a href=&quot;https://www.gnu.org/home.en.html&quot;&gt;GNU&lt;/a&gt;). Some projects choose to use GitLab over GitHub, as &lt;a href=&quot;https://gitlab.com/explore&quot;&gt;GitLab&lt;/a&gt; is open-source. However it is noted that Python project felt compelled to move from GitLab to GitHub because of its better developer tooling for automation (Working in Public, Page 30).
GitHub is popular largely because it&apos;s convenient, has strong developer tooling and a lower learning curve as a lot of developers are already familiar with its interface.&lt;/p&gt;
&lt;p&gt;Further Reading: &lt;a href=&quot;https://snarky.ca/the-history-behind-the-decision-to-move-python-to-github/&quot;&gt;The history behind the decision to move Python to GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Things like package managers have shifted how easy it is to install seemingly trivial but out-sourced logic. Tools like NPM and GitHub may have lowered the barrier to entry to contribute to or use open-source software but have they also increased the obligation of end-users to contribute to improving the software that they use? What are the responsibilities of a user of open-source software?&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;book&quot;&amp;gt;
&quot;I&apos;m just a &apos;user,&apos; and contributing a little is part of being a user&quot; (Mikeal
Rogers, Page 34, Working in Public)
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;As the community of folks who contribute to open-source grows, how can one be a &quot;good&quot; contributor or user? How can maintainers set up new contributors for success?&lt;/p&gt;
&lt;h3&gt;Effective onboarding is crucial&lt;/h3&gt;
&lt;p&gt;Part of the &quot;cost&quot; of onboarding new contributors is newcomers aren&apos;t intimately familiar with the norms and standards for a specific project. There are some universal contributing practices you can take from project to project but there are also certain things that are more salient or specific to a particular community. I think taking advantage of &lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub Actions&lt;/a&gt; to automate things like linting and testing as well as using and their &lt;a href=&quot;https://docs.github.com/en/github/building-a-strong-community&quot;&gt;tools for building a strong community&lt;/a&gt; can help alleviate some of the responsibility that falls on both maintainers and contributors.&lt;/p&gt;
&lt;p&gt;If you are working on a project that welcomes new contributors what tools, types of automation and documentation do you need to get newcomers up-to speed as quickly as possible? As new contributor I generally look for documentation in the form of READMEs and Contributor Guidelines or Code of Conducts.&lt;/p&gt;
&lt;h2&gt;The Structure of an Open Source project&lt;/h2&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;book&quot;&amp;gt;
&quot;Developers don&apos;t contribute to open source for lack of technical ability, but
rather due to fear of committing a faux pas.&quot; (Working in Public, Page 43)
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h3&gt;Where do you even start with contributing?&lt;/h3&gt;
&lt;p&gt;Contributing to open-source can seem more intimidating than someone&apos;s day to day work, regardless of skill-level required, as when new contributors often onboard to a new project they aren&apos;t familiar with the social conventions of the project. This is why if you&apos;re looking to onboard new contributors it&apos;s crucial to set them up for success by documenting certain norms in an obvious place (i.e, README, issue template, PR template).&lt;/p&gt;
&lt;p&gt;Open Source is not monolithic. Eghbal reminds us that &quot;The term &apos;open source&apos; refers only to how code is distributed and consumed. It says nothing about how it is produced&quot; (Working in Public, 44). There are certain trends within open source but a lot can vary from project to project.&lt;/p&gt;
&lt;h3&gt;What types of contributions are valued or accepted?&lt;/h3&gt;
&lt;p&gt;While open source projects allow contributions from everyone there is generally a review process and it may be more or less thorough from one project to another. I find that unless there&apos;s already an open issue that a maintainer has given a green light to or a glaring bug it&apos;s often best to run a change by a maintainer before going through the effort to implement it. If you want to add a one-off feature that mostly only benefits your use-case and you&apos;re not a significant stakeholder then your proposal likely won&apos;t make it past the approval process.&lt;/p&gt;
&lt;p&gt;I&apos;ve previously used Open-Source software that I wanted to function differently (i.e, adding pagination) but sometimes the maintainer would rather someone fork (copy and modify the software) as opposed to commit directly to the main repository and change that functionality for everyone. Maintainers balance different interests and can be opinionated in different ways about their project than contributors based on having more context regarding usage and long-term goals.&lt;/p&gt;
&lt;h3&gt;Getting the keys to an open-source project (write-access)&lt;/h3&gt;
&lt;p&gt;Getting commit access to a larger open source project unlocks the ability to directly merge changes into the main codebase and be more closely a part of the review process of PRs. How does one get commit access?&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;book&quot;&amp;gt;
&quot;I gained my commit privileges like people still do today on the Python
project: I consistently contributed good patches and eventually a core
developer noticed and asked if I wanted to join the team&quot; (Brett Cannon,
Working in Public, Page 48)
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;p&gt;There might not be just one way to get commit access to a project but I imagine a lot of people are similar to Brett and end up getting direct write access to a project once they&apos;ve shown that they have a long-term investment in contributing high-quality updates to a project. Most communities function similarly, generally when you&apos;re a positive contributor and more active within a community you are offered more responsibility to contribute back to a community.&lt;/p&gt;
&lt;p&gt;Eghbal points out that &quot;it&apos;s common among JavaScript developers to give away commit access more freely&quot; as Javascript &quot;is designed to be modular&quot; (Working in Public, Page 46). In the JavaScript ecosystem the benefits (distributing the responsibility of maintenance) of prematurely giving someone commit access may outweigh potential risks (a project being compromised but having minimal impact on the larger ecosystem). I&apos;ve mostly contributed to the JavaScript open-source community and therefore am not sure how easy or difficult it is to get write access to projects within other ecosystems but I can imagine the effects being devastating for projects that are less modular.&lt;/p&gt;
&lt;p&gt;An open-source project can extend beyond just a repository on GitHub. Its community can also live on Stack Overflow, mailing lists, Discord, etc.&lt;/p&gt;
&lt;h3&gt;Open-source projects are fluid and evolve over time.&lt;/h3&gt;
&lt;p&gt;There can be a creation phase where there&apos;s a lot of foundational work being put into the project and the project isn&apos;t seeking or advertising for outside contributors or users. After the creation phase there&apos;s an evangelism phase where open source authors want to encourage new users to try out their software and gather as much feedback as possible to use to iterate on the future development of the software. Some projects reach the growth phase where there is significantly more adoption and which Eghbal uses &quot;when maintainers start doing more non-code than code work on the project, such as triaging issues and reviewing others&apos; pull requests&quot; as a signifier of this stage being reached (Working in Public, 55). This growth phase is often when maintainers have to shift to be more selective with what type of contributions and issues they&apos;ll entertain from external contributors in order to be mindful of their time.&lt;/p&gt;
&lt;p&gt;I would think overall maintainers would be most eager for new external contributors during the evangelism phase as opposed to the creation or growth phase. I also wonder if a project can switch between the evangelism and growth phase...i.e, when a new version of Python is released, although Python is a mature project is there a huge initiative to encourage people to submit feedback and start adopting the newer version.&lt;/p&gt;
&lt;p&gt;Aside from projects changing over-time due to adoption and maturity Eghbal also mentions that there are 4 types of open source projects classified by user and contributor growth:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Federations - High User Growth, High Contributor Growth (very active open-source project, may be the first thing that comes to mind when you think of open source - Rust, Node, Linux, etc.)&lt;/li&gt;
&lt;li&gt;Stadiums - High User Growth, Low Contributor Growth (Widely used but don&apos;t have many contributors, i.e. Babel)&lt;/li&gt;
&lt;li&gt;Clubs - Low User Growth, High Contributor Growth (Niche programming languages such as Clojure and Haskell attract a narrow range of users and thus contributors)&lt;/li&gt;
&lt;li&gt;Toys - Low User Growth, Low Contributor Growth (i.e, a side project you didn&apos;t create with the intention of distributing to other developers although who knows it could develop traction)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Measuring the value of code&lt;/h2&gt;
&lt;p&gt;Eghbal brings up questions surrounding how do you measure the value of code? Is it based on the amount of dependencies and their value?&lt;/p&gt;
&lt;p&gt;Infamously the JavaScript library, &lt;a href=&quot;https://www.npmjs.com/package/left-pad&quot;&gt;Left-pad&lt;/a&gt;, broke a lot of things when it was briefly deleted from the NPM registry as it was an underlying dependency for important and prevalent software. Babel relied on left-pad and Babel is a dependency of millions of software projects. Once something is released into the open source universe it takes on a life of its own and isn’t just yours, it becomes part of a complex ecosystem. Due to the interdependent nature of software on an 11-line package (left-pad) NPM decided to restore the package against the original authors&apos; wishes.&lt;/p&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;book&quot;&amp;gt;
Do you think NPM should have restored left-pad or not? Read{&quot; &quot;}
&amp;lt;a href=&quot;https://qz.com/646467/how-one-programmer-broke-the-internet-by-deleting-a-tiny-piece-of-code/&quot;&amp;gt;
&quot;How one programmer broke the internet by deleting a tiny piece of
code&quot;{&quot; &quot;}
&amp;lt;/a&amp;gt;
to learn more about the left-pad incident.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h2&gt;Monetizing Open-Source&lt;/h2&gt;
&lt;p&gt;You might think that the inherent value derived from open-source software would make it easy to monetize, however, there are some things that make code difficult to monetize:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Developers are sensitive to price for a good that they could theoretically create themselves. Some software is more prone to this like web frameworks vs databases. Developers are more likely to build their own frontend technology solutions vs. infrastructure related solutions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The diversity and breadth of open-source is both a blessing and a curse when it comes to monetization. There’s a lot of variety in the open source world and a lot of software can be substituted or easily replaced with alternative open source solutions&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eghbal shares an example of &lt;a href=&quot;https://github.com/sindresorhus&quot;&gt;Sorhus&lt;/a&gt; who is able to work full-time in open source based on financial support from patreon and GitHub sponsors. However she believes that it’s his reputation and visibility and not the value of his software alone that has given him enough traction to live off of sponsorship. His sponsors aren’t sponsoring a specific project of his but overall investing in &lt;em&gt;him&lt;/em&gt; based on his reputation.&lt;/p&gt;
&lt;p&gt;Outside of crowd-sourced sponsorship from individuals, sometimes companies find it worthwhile to support a project’s maintainers as full time employees for reasons like branding, reputation purposes or to have a more direct influence on the future of a project.&lt;/p&gt;
&lt;h2&gt;The Law and Distribution of Software&lt;/h2&gt;
&lt;p&gt;The law can’t keep up with software.The US government used to regulate the export of cryptography related software and treated it as a munition export that required tighter regulations. The Pretty Good Privacy (PGP) author Phil Zimmerman ended up releasing the source code as a book PGP source code and internals which could then be more easily distributed around the world since it was protected under free speech (page 157) unlike the actual software in its digital form.&lt;/p&gt;
&lt;h2&gt;Participatory Open-Source&lt;/h2&gt;
&lt;p&gt;Eghbal explores various metaphors for how people interact with open source software. What stood out from the metaphors is that a lot of people expect open-source software to welcome participation regardless of the project. Some creators of open-source projects may be perfectly happy sharing their code freely but have no desire to spend additional time tweaking their library to appeal to other users as the intended user of their software is themselves and open-sourcing it is just a &quot;bonus&quot;. Eghbal compares this to how some people put up elaborate holiday light displays on their houses for their own enjoyment, as a byproduct their neighbors and travelers also enjoy their light display but the people who put up the lights are not planning on updating details of their light display based on feedback.&lt;/p&gt;
&lt;p&gt;“Much of the fatigue that open source developers experience comes not from making their code public but from the expectations around making their code participatory” (page 161).&lt;/p&gt;
&lt;p&gt;External participants to a software project often have different interests than maintainers and too many opinions outside of the core group that contributes to and maintains and project can lead to too many cooks in the kitchen.&lt;/p&gt;
&lt;h2&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;I enjoyed reading Working in Public and having the opportunity to discuss it with the &lt;a href=&quot;https://www.partycorgi.com/&quot;&gt;Party Corgi Community&lt;/a&gt;, an inclusive community of content creators helping each other grow. Eghbal&apos;s research and claims provided important historical context for open-source and pushed me to adopt a more critical lens to how I view the open-source ecosystem. I think the biggest takeaway for me, as someone who wants to make contributing to open-source more approachable (especially as a &lt;a href=&quot;https://stars.github.com/&quot;&gt;GitHub Star&lt;/a&gt;), is that it is crucial to proactively encourage meaningful contributions and to educate people on open-source norms in an effort to ease some of the responsibilities that are often placed on maintainers.&lt;/p&gt;
&lt;h2&gt;Further Reading (and listening)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Get a copy of &lt;a href=&quot;https://press.stripe.com/#working-in-public&quot;&gt;Working In Public&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Nadia Eghbal and Henry Zhu have a podcast, &lt;a href=&quot;https://hopeinsource.com/&quot;&gt;Hope in Source&lt;/a&gt; about faith and open source.&lt;/li&gt;
&lt;li&gt;Henry Zhu also has another podcast, &lt;a href=&quot;https://maintainersanonymous.com/&quot;&gt;Maintainers Anonymous&lt;/a&gt; that is focused on the experience of being a maintainer across various disciplines.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://joshwcomeau.com/blog/why-my-blog-is-closed-source/&quot;&gt;Why My Blog is Closed-Source&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;@richhickey&apos;s post &lt;a href=&quot;https://gist.github.com/richhickey/1563cddea1002958f96e7ba9519972d9&quot;&gt;&quot;Open Source is Not About You&quot;&lt;/a&gt; about his views on the boundaries between open-source creators and contributors&lt;/li&gt;
&lt;li&gt;&amp;lt;a href=&quot;https://calebporzio.com/i-just-hit-dollar-100000yr-on-github-sponsors-heres-how-i-did-it&quot;&amp;gt;
{&quot; &quot;}
I Just Hit $100k/yr On GitHub Sponsors! 🎉❤️ (How I Did It)
&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ethicalsource.dev/definition/&quot;&gt;The Ethical Open Source Movement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;Callout variant=&quot;garden&quot;&amp;gt;
That&apos;s all for now! 🥕 I finished my initial reading of the book but may
continue to update this post so check back later! The main content of this
post was last updated on September, 5th, 2020.
&amp;lt;/Callout&amp;gt;&lt;/p&gt;
&lt;h2&gt;Book Notes from other Party Corgis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.christopherbiscardi.com/working-in-public-by-nadia-eghbal&quot;&gt;Chris&apos; notes on Working in Public&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.willharris.dev/garden/working-in-public&quot;&gt;Will&apos;s notes on Working in Public&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lannonbr.com/blog/working-in-public-notes&quot;&gt;Benjamin&apos;s notes on Working in Public&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;&lt;p&gt;Note: If you encounter strange formatting in your RSS feed reader, I&apos;m still tweaking my RSS feed settings to display my content (which is written in MDX) properly. For a better content-viewing experience I encourage visiting the article directly.&lt;/p&gt;</content:encoded></item></channel></rss>