<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ryan Fitzgerald</title>
    <description>The latest articles on DEV Community by Ryan Fitzgerald (@rfitz).</description>
    <link>https://dev.to/rfitz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F477007%2F2c59379d-1c32-4947-9394-1a96a2a0d373.jpeg</url>
      <title>DEV Community: Ryan Fitzgerald</title>
      <link>https://dev.to/rfitz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rfitz"/>
    <language>en</language>
    <item>
      <title>Becoming an AI-native engineer</title>
      <dc:creator>Ryan Fitzgerald</dc:creator>
      <pubDate>Thu, 12 Jun 2025 15:00:26 +0000</pubDate>
      <link>https://dev.to/rfitz/becoming-an-ai-native-engineer-25gb</link>
      <guid>https://dev.to/rfitz/becoming-an-ai-native-engineer-25gb</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrx31n2ua735vea0imsx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrx31n2ua735vea0imsx.jpg" alt="AI" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’m confident I’m not alone in saying I’ve spent a lot of time over the past couple of years thinking about AI as an engineer / developer and what it means for our field and for our careers. For a long time, software engineering felt like a safe bet. After all, how could we be automated away when we’re the ones writing the automation? Or so we thought.&lt;/p&gt;

&lt;p&gt;Like many, I’ve run the full gamut of emotions on the topic: excitement, uncertainty, fear, frustration, more excitement, more uncertainty, and so on. If there's an emotional response to AI to be had, I’ve probably felt it. But lately, one emotion has &lt;em&gt;consistently&lt;/em&gt; stuck, and that’s &lt;strong&gt;excitement&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It took a while to get here (and arguably even longer to learn to stay here) but I can now say with confidence: the rise of AI excites me more than anything else right now as an engineer. The opportunities seem endless. Things have started to &lt;em&gt;click&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I’ve always considered myself a fairly effective engineer by traditional metrics...shipping consistently with quality, solving challenging technical problems, mentoring others. But with AI, I’m producing more than ever before, with a level of speed, quality, and understanding I’ve never experienced. In many ways, it’s 10x’d me as an engineer.&lt;/p&gt;

&lt;p&gt;The next sections of this post are my take on what it takes to become an AI-native software engineer and build fluency with AI at every level. I’m not claiming to have mastered it all (far from it) but I wanted to put my thoughts down in the hope that they might help another engineer or two get off the emotional rollercoaster and land on excitement. &lt;/p&gt;

&lt;p&gt;This post is less about specific tools and frameworks (though I’ll sprinkle in a few I've found) and more about the mental models needed to approach AI effectively. As I keep learning and refining my thinking (and as the space continues to evolve), I’ll aim to share more. For now, think of this as a brain dump of random thoughts I have on how I currently view AI’s role in modern software engineering. &lt;/p&gt;

&lt;h2&gt;
  
  
  It starts with a mindset shift
&lt;/h2&gt;

&lt;p&gt;The best way I can explain my mindset is that I see AI as a multiplier of myself, not just a tool that does work for me on command (though I'll admit “vibe coding” can be fun occasionally).&lt;/p&gt;

&lt;p&gt;Put another way: a junior engineer might only have the experience to ask AI questions that yield intermediate or senior-level responses. But a senior engineer, with deeper context and stronger instincts and understanding, can frame questions in ways that push the AI to think more like a Staff or Principal engineer, bringing a much sharper lens to the same problem. &lt;/p&gt;

&lt;p&gt;As your experience grows, so does your ability to prompt effectively and with that, the multiplier effect of AI scales alongside you. That’s why I often say I feel like I've 10x’d myself as an engineer. It’s like having a far more seasoned engineer in my corner with far more knowledge and context than I do, ready to answer any question or help debug any issue I run into. And the best part? I can't ever catch up to them; they stay steps ahead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The power of a good prompt
&lt;/h2&gt;

&lt;p&gt;Building on the previous point about how experience shapes AI output, it’s worth emphasizing that effective prompting is critical at every level. Even if you're more junior, you might not always know &lt;em&gt;what&lt;/em&gt; to ask but that doesn't mean you can't learn &lt;em&gt;how&lt;/em&gt; to ask it well with the experience you have.&lt;/p&gt;

&lt;p&gt;We’ve all seen it: you ask an LLM for code, and it spits out something completely unusable...maybe even the worst code you’ve ever seen. Is that the model’s fault? Maybe. But more often than not, it’s a sign that the prompt wasn’t clear or specific enough. With better framing, you can guide the model toward a much more useful and accurate response.&lt;/p&gt;

&lt;p&gt;Let’s walk through a quick example to illustrate this. Say you’ve built a simple React &lt;code&gt;LoginForm&lt;/code&gt; component and want to save time by having AI write the tests for it. So you type:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can you write tests for my React component?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sure, the LLM will likely produce &lt;em&gt;something&lt;/em&gt;, but chances are, it won’t be very good. Why? Because it has almost no context. It doesn’t know what the component does (it has to figure it out based on the code), what you’re trying to test, or what your expectations are. It’s forced to guess, and the result will reflect that. It might technically “work” but it definitely won’t be optimal. You can do better and save time spent reworking it later.&lt;/p&gt;

&lt;p&gt;Now compare that to a more thoughtful prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I built this &lt;code&gt;LoginForm&lt;/code&gt; React component that includes an email field, a password field, and a submit. It also includes a success and error state based on the result after calling &lt;code&gt;onSubmit&lt;/code&gt;. Can you write a test that: 1. renders the form, 2. fills in valid and invalid data 3. submits the form, 4. asserts that &lt;code&gt;onSubmit&lt;/code&gt; was called with the correct payload, and 5. checks both the success and error state render correctly as a result.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This takes only a bit more time to write, but the quality of the response will be exponentially better. The takeaway? Don’t cut corners on your prompt. A little extra effort upfront goes a long way.&lt;/p&gt;

&lt;p&gt;Another tip is if you're using an IDE like Cursor, take advantage of things like &lt;code&gt;.cursorrules&lt;/code&gt; to define project specific instructions. These give the LLM important context about your codebase and conventions you're looking to follow. Also, make use of the &lt;strong&gt;Ask vs. Agent&lt;/strong&gt; modes. Starting in Ask mode helps ensure the LLM fully understands your intentions before it starts generating or editing code. It may take a bit more time upfront, but it can save you from a lot of rework, confusion, and low-quality code suggestions that you ultimately end up rejecting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trust, but verify
&lt;/h2&gt;

&lt;p&gt;This one might seem like common sense and in some sense contradictory to previous statements, but it’s worth repeating: &lt;strong&gt;AI-generated output, especially code, should never be blindly trusted&lt;/strong&gt;. You’ve probably heard stories (more often than you’d like) of someone who "vibe coded" their authentication layer, only to have it exploited later by a third party.&lt;/p&gt;

&lt;p&gt;More often, the risk is subtler: the AI generates a block of code, you give it a quick skim, and it seems fine...so you ship it. But now you’ve got a production bug in code you don’t fully understand because you didn’t actually write it.&lt;/p&gt;

&lt;p&gt;AI-generated code is not bulletproof. Far from it. As previously discussed, the quality of the output is heavily dependent on the quality of your prompt. That’s why it’s critical to review everything carefully. Make sure you fully understand what the code is doing. Don’t hesitate to ask the LLM follow-up questions or have it review its own output for potential bugs or optimizations. Treat fully generated code (no matter how complex) like it was produced by a junior engineer: helpful, but in need of oversight.&lt;/p&gt;

&lt;p&gt;Also, don’t be afraid to push back and provide feedback based on your own experience. Just because AI can act as a multiplier and draws from a broader base of knowledge, doesn’t mean your expertise isn’t valuable. &lt;strong&gt;Trust your instincts and experience&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It’s not uncommon for AI-generated code to be unnecessarily over-engineered. For example, I recently asked it to help with DNS lookups in Node.js, and I wanted the operation to timeout after a set number of seconds. The AI’s response was mostly correct, but for the timeout, it built a fully manual workaround. Fortunately, I had used the library before and knew it supported a built-in timeout option. I shared that with the model, and it simplified the solution accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code generation vs. deeper understanding
&lt;/h2&gt;

&lt;p&gt;The power of AI goes far beyond just generating code for engineers. While that’s certainly one of its most impressive capabilities, it shouldn’t be the only way you benefit from it. One of the most underrated use cases is using AI to help you understand complex topics...breaking down ideas, explaining unfamiliar concepts, or walking through problems step-by-step.&lt;/p&gt;

&lt;p&gt;For example, want to support custom domains for your customers but aren’t sure where to start with DNS configuration or SSL certificate generation? Or maybe you're trying to set up scalable email infrastructure? Or manage large volumes of data efficiently? These are all tasks that, in the past, would have required extensive research, lots of trial and error, or a more experienced engineer to walk you through. With AI, those learning curves can shrink from days or weeks to potentially hours.&lt;/p&gt;

&lt;p&gt;AI is an incredible tool not just for generating code, but for helping engineers understand architectural patterns, validate existing systems, and solidify their understanding of complex topics. Use it for those purposes just as much as you use it to write code.&lt;/p&gt;

&lt;p&gt;That said, the same caution applies: don’t take architectural advice at face value, especially in areas where your own experience is limited. Just like you’d review AI-generated code before shipping it, review architectural suggestions carefully and make sure you truly understand the tradeoffs before adopting them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Never stop learning
&lt;/h2&gt;

&lt;p&gt;As engineers, continuous learning tends to come naturally but in the context of AI, it’s absolutely essential. The landscape is evolving at an incredible pace, and the tools, frameworks, and capabilities available today might look completely different a year from now. Staying current with the latest developments, trends, and techniques is key to remaining effective and fluent.&lt;/p&gt;

&lt;p&gt;To keep up, I rely on a mix of resources: newsletters, blogs, X (Twitter) accounts, and even Reddit threads. These help surface what's new, what’s gaining traction, and what’s worth exploring further.&lt;/p&gt;

&lt;p&gt;There are also plenty of excellent resources available for deeper learning...whether you want to explore specific topics, understand the inner workings of LLMs, or sharpen your skills with real-world examples. Whether you're casually exploring or diving deep, there’s no shortage of high-quality content to learn from.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick plug:&lt;/strong&gt; If you're looking for a curated source to stay up-to-date with the latest AI news, trends, and tools for busy developers, I publish a weekly &lt;strong&gt;AI Dev Roundup&lt;/strong&gt; newsletter. No fluff, just the good stuff. It's effectively a collection of what I find throughout the week. &lt;a href="https://aidevroundup.com/?ref=devto" rel="noopener noreferrer"&gt;Check it out →&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Finding the right tools
&lt;/h2&gt;

&lt;p&gt;Finding the right tools for your AI workflows is a personal process and it’s highly subjective. It depends on how you like to work. My best advice: &lt;strong&gt;try as many tools as you can and see what fits your style and needs best&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At the IDE level, there are some excellent options like &lt;strong&gt;Cursor&lt;/strong&gt;, &lt;strong&gt;Windsurf&lt;/strong&gt;, and &lt;strong&gt;GitHub Copilot in VS Code&lt;/strong&gt;. Each has its own strengths, and it’s worth exploring what makes each one unique. There are also cost components to each if you want to leverage all features.&lt;/p&gt;

&lt;p&gt;Beyond IDEs, there are also powerful standalone AI agents built for software engineering tasks. Tools like &lt;strong&gt;Claude Code&lt;/strong&gt;, &lt;strong&gt;Cline&lt;/strong&gt;, &lt;strong&gt;OpenAI Codex&lt;/strong&gt;, and others. These can be great companions for deeper problem-solving, architecture planning, or even long-form code generation.&lt;/p&gt;

&lt;p&gt;Once you find a tool that clicks, become a power user. Learn its strengths, shortcuts, and workflows. Personally, I’ve landed on &lt;strong&gt;Cursor&lt;/strong&gt;. It’s an incredibly capable AI IDE that covers everything I need and then some. I also appreciate how active their team is in the community and how committed they are to continually improving the product for engineers.&lt;/p&gt;

&lt;p&gt;Another important factor to consider is the models themselves. It’s well worth experimenting with different models and staying up to date on which ones perform best for software engineering tasks because this changes often.&lt;/p&gt;

&lt;p&gt;Some models are stronger at reasoning, others at code generation, and some handle multi-step tasks more effectively. Knowing which model excels in which area can give you a serious edge especially in tools like Cursor, where you can choose which model powers your agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build. Build. Build some more.
&lt;/h2&gt;

&lt;p&gt;This one almost goes without saying: &lt;strong&gt;build&lt;/strong&gt;. &lt;strong&gt;Then build some more&lt;/strong&gt;. The best way to understand what’s possible with AI as an engineer is to experiment. Try building new things, use AI in different ways, create powerful workflows, and explore not only its strengths but begin to understand its limitations.&lt;/p&gt;

&lt;p&gt;The more you apply it hands-on, the better you'll understand where it excels, where it falls short, and how to get the most out of it. That’s how you truly become AI-native as an engineer.&lt;/p&gt;

&lt;p&gt;Building with AI isn’t just about generating code, it’s also about building products that integrate AI at their core. Some of my biggest leaps in understanding came from working directly with AI inside real products, especially when implementing agentic workflows using frameworks like LangGraph. The more you're in the weeds, the more you're forced to learn, experiment, and debug.&lt;/p&gt;

&lt;p&gt;Working with AI in this way forces you to think about system design, user interaction, reliability, and how AI fits into real-world use cases, which is where the deeper learning really happens. It also very quickly reinforces the power of a strong prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's not always all about the code
&lt;/h2&gt;

&lt;p&gt;Being an AI-native engineer isn’t just about generating code. Sure, you might already use it to draft emails, write pull request descriptions, or even respond to Slack messages, but there are many powerful use cases that often get overlooked.&lt;/p&gt;

&lt;p&gt;For example, imagine you’re in a meeting discussing a major architectural decision, and you notice some serious flaws in the proposed approach. Maybe you're experiencing some difficulty trying to articulate your concerns in a way that resonates with the team. That’s a perfect moment to leverage AI: give it context, explain your concerns, and let it help you dig deeper, explore the implications, and even frame your argument more clearly and persuasively.&lt;/p&gt;

&lt;p&gt;The point is, AI doesn’t just have to help you write or understand code, it can support you in every facet of your role as an engineer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;I know this was more of a high-level piece, but I hope it offered some useful mindsets or mental models for applying AI as a software engineer. If you found it helpful or have any feedback, &lt;a href="https://x.com/rfitzio" rel="noopener noreferrer"&gt;I’d love to hear from you&lt;/a&gt;. I plan to continue writing on this topic, talking about AI workflows as an engineer, and may dive deeper into specific areas in future posts as well.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why your team probably doesn't need a synchronous standup</title>
      <dc:creator>Ryan Fitzgerald</dc:creator>
      <pubDate>Tue, 03 Oct 2023 01:31:08 +0000</pubDate>
      <link>https://dev.to/rfitz/why-your-team-probably-doesnt-need-a-synchronous-standup-41jf</link>
      <guid>https://dev.to/rfitz/why-your-team-probably-doesnt-need-a-synchronous-standup-41jf</guid>
      <description>&lt;p&gt;In recent years, the landscape of remote work and collaboration has changed considerably, and with that, the way teams communicate and share updates has also undergone a bit of a transformation. Traditional synchronous standup meetings, a staple in many workplaces, are increasingly being challenged by the more flexible and efficient approach of asynchronous standup options. In this blog post, we will explore why asynchronous standups might be the way forward for your team.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Rise of Asynchronous Work
&lt;/h3&gt;

&lt;p&gt;The shift towards asynchronous work has been driven by the growing acceptance of remote work, especially since the pandemic. With team members spread across different time zones and schedules, it becomes challenging to coordinate meetings that suit everyone. Asynchronous work acknowledges and embraces this reality, allowing individuals to contribute when it's convenient for them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flexibility and Inclusivity
&lt;/h3&gt;

&lt;p&gt;One of the significant advantages of asynchronous standups is the flexibility they offer. Team members can participate in the standup at their own pace, eliminating the need to synchronize schedules with the rest of the team. This inclusivity is particularly important when you have team members located in different regions or those with varying work-hour preferences.&lt;/p&gt;

&lt;p&gt;In contrast, traditional synchronous standups can inadvertently exclude team members who cannot attend due to conflicting schedules or time zone differences. Asynchronous standups ensure that everyone has an equal opportunity to contribute and stay updated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Standup Automation
&lt;/h3&gt;

&lt;p&gt;The increasing popularity of asynchronous standups has given rise to a number of popular tools that help automate the async standup process in existing tools your team probably already uses, such as Slack. For example, tools like &lt;a href="https://standupwizard.com" rel="noopener noreferrer"&gt;StandupWizard&lt;/a&gt; will message your team on Slack individually at your defined schedule and post their responses to a central channel for all to see, among other features. &lt;/p&gt;

&lt;h3&gt;
  
  
  Enhanced Productivity
&lt;/h3&gt;

&lt;p&gt;Asynchronous standups can also boost productivity by minimizing interruptions. In a traditional standup, everyone is expected to be present and attentive at the same time. This can disrupt the flow of individual work and lead to productivity losses. With asynchronous standups, team members can choose when to review and respond to updates, allowing them to allocate their focus where it's needed most.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Preparedness
&lt;/h3&gt;

&lt;p&gt;Asynchronous standups encourage team members to think more carefully about their updates. When sharing their progress in a written or recorded format, they have the time to articulate their thoughts clearly and concisely. This leads to more meaningful contributions and a better understanding of each team member's responsibilities and progress.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improved Documentation
&lt;/h3&gt;

&lt;p&gt;Documentation is a key aspect of effective project management. Asynchronous standups naturally generate written or recorded updates, creating a valuable record of the team's activities and decisions. This documentation can be referenced in the future, serving as a knowledge base for new team members or for reviewing past projects and their outcomes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overcoming Time Zone Challenges
&lt;/h3&gt;

&lt;p&gt;For globally distributed teams, asynchronous standups are a lifesaver. Team members can provide updates without needing to be online at the same time. This eliminates the challenges associated with time zones and makes collaboration more seamless and efficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;While asynchronous standups offer a wide array of benefits, it's important to acknowledge that they may not be suitable for every team or situation. The transition from traditional synchronous standups to asynchronous ones should be done while taking into account the team's preferences, needs, and existing workflows. By embracing this shift, teams can work more efficiently, collaboratively, and adapt to the changing dynamics of the workplace.&lt;/p&gt;

</description>
      <category>standup</category>
      <category>webdev</category>
      <category>programming</category>
      <category>startup</category>
    </item>
    <item>
      <title>How to 301 Redirect to a new domain with AWS S3 &amp; CloudFront</title>
      <dc:creator>Ryan Fitzgerald</dc:creator>
      <pubDate>Thu, 09 Mar 2023 00:11:49 +0000</pubDate>
      <link>https://dev.to/rfitz/how-to-301-redirect-to-a-new-domain-with-aws-s3-cloudfront-7ob</link>
      <guid>https://dev.to/rfitz/how-to-301-redirect-to-a-new-domain-with-aws-s3-cloudfront-7ob</guid>
      <description>&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;Recently I made the decision to rebrand a &lt;a href="https://extensionkit.io/?ref=devto" rel="noopener noreferrer"&gt;side project of mine&lt;/a&gt; which involved changing the domain name from &lt;strong&gt;ChromeExtensionKit.com&lt;/strong&gt; to &lt;strong&gt;ExtensionKit.io&lt;/strong&gt;. At it's core, it wasn't a difficult move, however the old domain had reasonably good SEO and search ranking so I wanted to ensure that I maintained that as much as possible through a permanent 301 redirect of the old domain. The main challenge was that the domain pointed to a single static site so I didn't have easy access to a server where I could add a simple &lt;code&gt;.htaccess&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I looked at a couple of options such as spinning up a quick Lambda to perform the 301 redirect and even looked at handling the redirection at the DNS level, however I ultimately settled on the AWS S3 and CloudFront approach as it was both the easiest and cheapest option. This blog post will go over how to setup a 301 permanent redirect from an old domain to a new domain with HTTPS support and 1:1 mapping of paths (for example, &lt;code&gt;oldsite.com/page1&lt;/code&gt; redirects to &lt;code&gt;newsite.com/page1&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;For this walkthrough, we will assume that we are moving a fake domain &lt;strong&gt;oldsite.com&lt;/strong&gt; to a new (also fake) domain &lt;strong&gt;newsite.com&lt;/strong&gt;. This will also assume that the new domain (newsite.com in this example) is already setup and supports HTTPS traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Configuring Route53
&lt;/h2&gt;

&lt;p&gt;The first step is to create a new Hosted Zone in AWS Route53. To get started, login to the AWS console and head over to Route53. Once there, simply add in your old domain that you'll be moving into the domain name field. All other options can be kept as the defaults.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa2ni7fywud7vbdf2zbag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa2ni7fywud7vbdf2zbag.png" alt="Route53" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, go to where you have your old domain hosted and update it to point to the nameservers provided by AWS. We will come back to Route53 to connect AWS resources later.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Creating a certificate
&lt;/h2&gt;

&lt;p&gt;After pointing your old domain to AWS, you'll need to ensure it utilizes HTTPS. This can be accomplished easily within AWS by heading over to Certificate Manager. Once there, click &lt;strong&gt;Request a certificate&lt;/strong&gt; and ensure public certificate is selected. After that, enter your domain you just added in step 1 under the Fully Qualified Domain Name field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fanchn9n6mczyawpuhp2x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fanchn9n6mczyawpuhp2x.png" alt="Request certificate" width="800" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After creating, you will see that the domain requires validation to prove that you own it. Under the &lt;strong&gt;Domains&lt;/strong&gt; section, click the &lt;strong&gt;Create records in Route 53&lt;/strong&gt; button which will open Route53 once again where you can add the required txt record that proves ownership. Once that is complete, you should see status of &lt;strong&gt;Issued&lt;/strong&gt; on the certificate page after a bit of time.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Setting up S3
&lt;/h2&gt;

&lt;p&gt;Next we will setup the redirecting mechanism. This is accomplished via S3 in website hosting mode. Head over to S3 and create a new bucket with the name of your old domain (or some other unique name) and ensure &lt;strong&gt;Block all public access&lt;/strong&gt; is unchecked.&lt;/p&gt;

&lt;p&gt;Once created, navigate into the bucket and open the &lt;strong&gt;Properties&lt;/strong&gt; tab. At the bottom, click &lt;strong&gt;Edit&lt;/strong&gt; under &lt;strong&gt;Static website hosting&lt;/strong&gt;. Once there, enable &lt;strong&gt;Static website hosting&lt;/strong&gt; and choose &lt;strong&gt;Redirect requests for an object&lt;/strong&gt; as the hosting type. Finally, enter your new domain as the host name and choose &lt;strong&gt;https&lt;/strong&gt; as the protocol. This should look like the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0orfjt1a7zkzlypq6lk4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0orfjt1a7zkzlypq6lk4.png" alt="S3 website hosting" width="800" height="623"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After creating it, copy down the &lt;strong&gt;Bucket website endpoint&lt;/strong&gt; as you'll need this later when setting up CloudFront. It should be in the form of &lt;code&gt;http://{bucketname}.s3-website-{region}.amazonaws.com&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Setting up CloudFront
&lt;/h2&gt;

&lt;p&gt;Now that we have the old domain setup and the redirect to the new domain in place, we need to tie it together and support HTTPS traffic via CloudFront (as the current redirect will not support HTTPS redirection). Head over to CloudFront and click &lt;strong&gt;Create distribution&lt;/strong&gt; to create a new distribution.&lt;/p&gt;

&lt;p&gt;Under the &lt;strong&gt;Origin&lt;/strong&gt; section, enter your bucket website endpoint URL you copied in Step 3 above and put that into the &lt;strong&gt;Origin domain&lt;/strong&gt;. It is important that you paste in the S3 website endpoint and not select the bucket itself or it will not work as expected. Every other setting in this section can be left as the default.&lt;/p&gt;

&lt;p&gt;Next, under the &lt;strong&gt;Settings&lt;/strong&gt; section, either keep the &lt;strong&gt;Price class&lt;/strong&gt; as the default or change to another to slightly reduce cost (for example, NA and Europe). After that, click &lt;strong&gt;Add item&lt;/strong&gt; under &lt;strong&gt;Alternate domain name (CNAME)&lt;/strong&gt; and add your old domain (in this case, we will use &lt;code&gt;oldsite.com&lt;/code&gt;). Finally, under the &lt;strong&gt;Custom SSL certificate&lt;/strong&gt;, find and select the certificate created in Step 2 (in should appear in the list if done correctly). Every other setting in this section can be left as the default.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffa9snhax1jv6kdy8scxe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffa9snhax1jv6kdy8scxe.png" alt="Cloudfront settings" width="800" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, click &lt;strong&gt;Create distribution&lt;/strong&gt; to finish the process and wait for the distribution status to change to &lt;strong&gt;Enabled&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Tying it all together
&lt;/h2&gt;

&lt;p&gt;The final step in the process is to head back over to Route53 and tie everything together. Open up Route53 and head into your Hosted Zone created in Step 1. Once there, click &lt;strong&gt;Create Record&lt;/strong&gt; and choose &lt;code&gt;A&lt;/code&gt; as the Record type and enable the &lt;strong&gt;Alias&lt;/strong&gt; checkbox. From there, select &lt;strong&gt;Alias to CloudFront distribution&lt;/strong&gt; in the first dropdown and select the distribution you just created in the second dropdown. This should look something like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56ymamdh424do2ba5sbt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56ymamdh424do2ba5sbt.png" alt="Route53 linked to CloudFront" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once complete, give it some time to fully propagate and you should be ready to verify. This final step effectively linked the domain to CloudFront, which behind the scenes uses the certificate we created to enable HTTPS and pushes traffic to our S3 bucket which in turn redirects to the new domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Verifying the redirect
&lt;/h2&gt;

&lt;p&gt;After completing steps 1-5, you are ready to verify the redirect is working as intended. It is worth noting that if you don't see the changes active immediately, you may need to give it a bit of time to propagate fully. Once you've given it enough time, you can either visit the old url and enure you're automatically redirected to the new one, or you can perform a &lt;code&gt;curl&lt;/code&gt; command that'll also return the 301 status. To perform this, simply enter the following into your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -I https://oldsite.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running, you should see something similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/2 301
content-length: 0
location: https://newsite.com
date: Fri, 03 Mar 2023 21:48:18 GMT
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 someid.cloudfront.net (CloudFront)
x-amz-cf-pop: some-cf-pop
x-amz-cf-id: some-cf-id
age: 10000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should notice at this point that the status is &lt;strong&gt;301&lt;/strong&gt; signifying the permanent redirect and the &lt;strong&gt;location&lt;/strong&gt; is your new domain.&lt;/p&gt;

&lt;p&gt;One final step that is beneficial when performing a domain move is to notify Google so you don't lose SEO rankings. For more information on their Change of Address Tool, &lt;a href="https://support.google.com/webmasters/answer/9370220?hl=en" rel="noopener noreferrer"&gt;click here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;You can setup a permanent 301 Redirect from an old domain to a new one easily and cost effectively using AWS. Make sure your new domain is active and supports HTTPS, then perform the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add your old domain Route53&lt;/li&gt;
&lt;li&gt;Create a certificate for the old domain using Certificate Manager&lt;/li&gt;
&lt;li&gt;Create an S3 bucket in website hosting mode and redirect to your new domain&lt;/li&gt;
&lt;li&gt;Create a CloudFront distribution for the old domain using the cert from step 2 to support HTTPS and link to S3 website endpoint created in step 3&lt;/li&gt;
&lt;li&gt;Add an Alias record in Route53 for the old domain that points to the CloudFront distribution in step 4&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At a high level, this strategy uses S3 website hosting mode to redirect to the new domain by creating a CloudFront distribution in front of the old domain (to allow for HTTPS redirects) pointing to the S3 bucket.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>aws</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Understanding the useEffect Hook in React</title>
      <dc:creator>Ryan Fitzgerald</dc:creator>
      <pubDate>Tue, 27 Oct 2020 14:20:09 +0000</pubDate>
      <link>https://dev.to/rfitz/understanding-the-useeffect-hook-in-react-2m4h</link>
      <guid>https://dev.to/rfitz/understanding-the-useeffect-hook-in-react-2m4h</guid>
      <description>&lt;p&gt;Since their release in React 16.8, hooks have quickly become a powerful tool in any React developers toolbox. One of the default hooks I find myself using all the time is &lt;code&gt;useEffect&lt;/code&gt;, which allows you to perform side effects in your functional components.&lt;/p&gt;

&lt;p&gt;Although &lt;code&gt;useEffect&lt;/code&gt; is useful for managing side effects in React apps (data fetching, manual DOM manipulation, and so on), it can often be a source of confusion for those who haven't run into it before, and more importantly, it can negatively impact performance of your app if used incorrectly.&lt;/p&gt;

&lt;p&gt;The most important thing to understand with the &lt;code&gt;useEffect&lt;/code&gt; hook is that it &lt;em&gt;attempts&lt;/em&gt; to runs after every single render of the component (including initial render) it is defined in. With that said, you can customize how often the &lt;code&gt;useEffect&lt;/code&gt; logic runs in your component fairly easily. It's also worth noting that &lt;code&gt;useEffect&lt;/code&gt; only gets run after the browser has painted, meaning it doesn't block the browser from updating.&lt;/p&gt;

&lt;p&gt;In the next couple sections, I'll discuss the various possibilities for running &lt;code&gt;useEffect&lt;/code&gt; as well as provide some examples and compare it to the class lifecycle methods were appropriate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run Every Render
&lt;/h2&gt;

&lt;p&gt;By default, &lt;code&gt;useEffect&lt;/code&gt; will run on initial render as well as every future render (update) of your component. This basic usage looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I run on every render&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To further clarify this, let's take an example from the &lt;a href="https://reactjs.org/docs/hooks-effect.html" rel="noopener noreferrer"&gt;React docs&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Example&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`You clicked &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; times`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Clicked &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; times&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Click me&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a basic counter component that increments the counter (using state) and changes the page title (side effect) every time the button is clicked.&lt;/p&gt;

&lt;p&gt;So how does this work? When the button is clicked, the &lt;code&gt;count&lt;/code&gt; state variable is updated. As a result of state being updated, the component re-renders and then the &lt;code&gt;useEffect&lt;/code&gt; is triggered, which in turn updates the document (page) title.&lt;/p&gt;

&lt;p&gt;Although not fully the same, this usage would be similar to using a combination of &lt;code&gt;componentDidMount&lt;/code&gt; (to cover the initial render) and &lt;code&gt;componentDidUpdate&lt;/code&gt; (to cover future updates) in class components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run Once
&lt;/h2&gt;

&lt;p&gt;Now that we've seen the default case, how can we customize the &lt;code&gt;useEffect&lt;/code&gt; hook to run only once (i.e. on initial render)? There is a second and optional argument of &lt;code&gt;useEffect&lt;/code&gt;, which is a dependency array. If we want to run it only once, we can define it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I run once, on initial render&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When your component re-renders, &lt;code&gt;useEffect&lt;/code&gt; will first check the dependency array provided to it and only run if one of the dependencies have changed. In this case, we provide an empty dependency array, so nothing will ever change, hence only being run once on initial render.&lt;/p&gt;

&lt;p&gt;Common use cases for only running on initial render may be to fetch data or to change the page title. Once again, this can be compared to &lt;code&gt;componentDidMount&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run on Dependency Change
&lt;/h2&gt;

&lt;p&gt;Now that we know &lt;code&gt;useEffect&lt;/code&gt; has an optional second argument, we can use that to customize it to run only on dependency change (such as state or props, for example). This would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I run every time myVar changes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;myVar&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example, the &lt;code&gt;useEffect&lt;/code&gt; logic would be run on the initial render, and then every subsequent render where &lt;code&gt;myVar&lt;/code&gt; has changed in value. If &lt;code&gt;myVar&lt;/code&gt; hasn't changed between renders, it won't run.&lt;/p&gt;

&lt;p&gt;To clarify further, let's take the original counter example and convert it to use the dependency array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Example&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`You clicked &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; times`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Clicked &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; times&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Click me&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every time the count is incremented and the component is re-rendered, it will change the document title, similar to above.&lt;/p&gt;

&lt;p&gt;You may be thinking, why bother providing the dependency array if the first example worked just fine? In this case, there isn't much difference and either option works. However, as soon as you add an additional piece of code to the original example (without the dependency array) that causes re-renders as well (such as additional state), you run the risk of the &lt;code&gt;useEffect&lt;/code&gt; block running more than it needs to, since it runs on every render. With that in mind, it's typically good practice to provide the dependency array unless you have a specific reason not to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run on Clean Up
&lt;/h2&gt;

&lt;p&gt;The final case I'll be covering is the clean up case. This version is typically used when subscribing to something, such as sockets, as you'll also want to unsubscribe when the component is no longer mounted. This pattern would look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I run on every render&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I run on clean up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above snippet, we return a function from the &lt;code&gt;useEffect&lt;/code&gt; which tells it what to run on clean up. When clean up is triggered (i.e. component unmounts), the code inside would get triggered. For example, we may want our component to subscribe to sockets on render and then unsubscribe to the sockets when that component unmounts as they are no longer needed.&lt;/p&gt;

&lt;p&gt;Another point worth making here is that you can still use the dependency array discussed above in the same way as this will not impact how that works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using More Than Once
&lt;/h2&gt;

&lt;p&gt;Similar to other hooks such as &lt;code&gt;useState&lt;/code&gt;, you can also use &lt;code&gt;useEffect&lt;/code&gt; multiple times in one component.&lt;/p&gt;

&lt;p&gt;For example, let's take the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Example&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;myProp&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Do something on initial render only, like changing document title&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Do something every time a prop changes, like fetch some additional data&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;myProp&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// ... Rest of the component&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above component, it would run the first &lt;code&gt;useEffect&lt;/code&gt; only on initial render, which may be responsible for setting the page title, for example. The second &lt;code&gt;useEffect&lt;/code&gt; may be used to fetch data based on a prop and would also be run on initial render, but it will also get run every time the component re-renders and &lt;code&gt;myProp&lt;/code&gt; has changed.&lt;/p&gt;

&lt;p&gt;This pattern is helpful if you have side effects that happen at different times and in different ways within your component.&lt;/p&gt;

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

&lt;p&gt;I hope you found this high-level overview of the &lt;code&gt;useEffect&lt;/code&gt; hook helpful! If you have any questions or feedback, feel free to &lt;a href="https://twitter.com/rfitzio" rel="noopener noreferrer"&gt;reach out on Twitter&lt;/a&gt; or comment below.&lt;/p&gt;

&lt;p&gt;If you'd like to go a little deeper on the &lt;code&gt;useEffect&lt;/code&gt; hook and how it works under the hood, the &lt;a href="https://reactjs.org/docs/hooks-effect.htm" rel="noopener noreferrer"&gt;Official React docs page&lt;/a&gt; is an excellent resource.&lt;/p&gt;

</description>
      <category>react</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Error Tracking in Chrome Extensions</title>
      <dc:creator>Ryan Fitzgerald</dc:creator>
      <pubDate>Thu, 22 Oct 2020 01:01:59 +0000</pubDate>
      <link>https://dev.to/rfitz/error-tracking-in-chrome-extensions-38j2</link>
      <guid>https://dev.to/rfitz/error-tracking-in-chrome-extensions-38j2</guid>
      <description>&lt;p&gt;So you've built a Chrome Extension and published it to the store, but how do you ensure it's running smoothly for your users? Unlike a normal web service, it's a bit tougher to figure out when things go wrong within a Chrome Extension and even more frustrating to try and recreate the issue while attempting to debug it. It's important to try and figure out a way to track errors that pop up before they appear in the form of a bad review on your extension.&lt;/p&gt;

&lt;p&gt;Fortunately, there are a number of error logging services available that can be added to your Chrome Extension with just a little bit of work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a Service
&lt;/h2&gt;

&lt;p&gt;The first step is figuring out which service you'd like to use. There are an endless amount of error monitoring and reporting tools out there, so I'll list a few you can look into in order to see which fits your needs. Some of these include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.honeybadger.io/" rel="noopener noreferrer"&gt;HoneyBadger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rollbar.com/" rel="noopener noreferrer"&gt;Rollbar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.bugsnag.com/" rel="noopener noreferrer"&gt;BugSnag&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://airbrake.io/" rel="noopener noreferrer"&gt;AirBrake&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;And many more!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the purpose of this walkthrough, I'm going to go ahead and choose Sentry. I've used the service many times in the past and love how easy it is to get setup, plus they have a pretty decent free plan if you're just getting started. If you'd like to follow along using Sentry, just head over to their site and sign up for an account, if not, feel free to use your own tool and simply change it to your tool's config when we add the setup details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;To get started, I'm going to head into the Sentry Dashboard and create a new Project. You'll find the button in the top right of your Projects page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3wgcq9ppfqdyrgvz4jz9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3wgcq9ppfqdyrgvz4jz9.png" alt="Create Project" width="310" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, I'm going to choose &lt;strong&gt;Browser Javascript&lt;/strong&gt; as the project type. If your extension uses NPM or Yarn (i.e. you are building with React, etc), then you should pick simply &lt;strong&gt;Javascript&lt;/strong&gt; as it will walk you through installing the package via NPM. Most providers should also have a similar basic JavaScript variation. Once you create your project, you will most likely be provided with some instructions to add a script similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://browser.sentry-cdn.com/5.23.0/bundle.min.js"&lt;/span&gt;
  &lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha384-5yYHk2XjpqhbWfLwJrxsdolnhl+HfgEnD1UhVzAs6Kd2fx+ZoD0wBFjd65mWgZOG"&lt;/span&gt;
  &lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the corresponding &lt;code&gt;init&lt;/code&gt; function looking something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;dsn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-sentry-dsn-here&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are 2 ways we can actually add this to our extension. The first, and easiest way, is if your extension has it's own HTML page (i.e. new tab, popup, etc) which allows you to simply add the above script tag and to init the script via your own JS file. The second option is if your extension runs in the background, in which case you would need to dynamically inject the above script tag and init it afterwards, most likely via background scripts.&lt;/p&gt;

&lt;p&gt;For the sake of this simple walkthrough, we'll go over the first method in the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding To Your Extension
&lt;/h2&gt;

&lt;p&gt;Let's start by creating a new example extension with 3 files: &lt;code&gt;manifest.json&lt;/code&gt;, &lt;code&gt;index.html&lt;/code&gt;, and &lt;code&gt;scripts.js&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;manifest.json&lt;/code&gt;: the extension manifest file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index.html&lt;/code&gt;: the HTML for our sample extension&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scripts.js&lt;/code&gt;: this is the script we load into our page and other than initializing Sentry, it would also include your overall extension logic most likely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;manifest.json&lt;/code&gt; file will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Error Tracking Example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"browser_action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"default_popup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"default_title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Open Popup"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content_security_policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self' https://browser.sentry-cdn.com; object-src 'self'"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's important to note that the &lt;code&gt;content_security_policy&lt;/code&gt; portion is what allows our Sentry script to load and communicate cross-origin (i.e. send the error back). By default, Chrome Extensions disable all cross-origin requests to mitigate potential cross-site scripting attacks. In this case, we are letting Chrome know that we want to load from the specific Sentry origin. For more information, &lt;a href="https://developer.chrome.com/extensions/contentSecurityPolicy" rel="noopener noreferrer"&gt;click here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;index.html&lt;/code&gt; file will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Error Tracking Example&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"css/index.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Hello World!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script
      &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://browser.sentry-cdn.com/5.23.0/bundle.min.js"&lt;/span&gt;
      &lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha384-5yYHk2XjpqhbWfLwJrxsdolnhl+HfgEnD1UhVzAs6Kd2fx+ZoD0wBFjd65mWgZOG"&lt;/span&gt;
      &lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"scripts.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, the &lt;code&gt;scripts.js&lt;/code&gt; file will look like (init for Sentry and our extension logic, which is just an error in this example):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Init sentry&lt;/span&gt;
&lt;span class="nx"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;dsn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-dsn-here&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Call a random, undefined function. This will cause an error&lt;/span&gt;
&lt;span class="nf"&gt;myUndefinedFunction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing It Out
&lt;/h2&gt;

&lt;p&gt;Now that we have our test extension setup, navigate over to &lt;code&gt;chrome://extensions/&lt;/code&gt; and make sure you have &lt;em&gt;Developer Mode&lt;/em&gt; enabled.&lt;/p&gt;

&lt;p&gt;Next, load the unpacked extension and click the extension icon in the browser bar.&lt;/p&gt;

&lt;p&gt;If you navigate back to &lt;code&gt;chrome://extensions/&lt;/code&gt;, you should see errors have popped up for the extension locally. If we didn't have error monitoring, a user could run into this situation and we would have no idea that it happened nor could we reproduce it (unless they provided us with necessary information).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F091v7jvn6cicqvk05f05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F091v7jvn6cicqvk05f05.png" alt="Local errors" width="800" height="596"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This isn't great because this may result in some negative reviews on the Web Store before we can even attempt to fix it. Fortunately, error handling has us covered here.&lt;/p&gt;

&lt;p&gt;In order to see it in action, we can navigate back to our Sentry Dashboard and see if that error has been captured and sent to us there. If all goes well, you should see something similar to the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgk5ffj602kjplk1jqejy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgk5ffj602kjplk1jqejy.png" alt="Sentry Example" width="800" height="136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you were to click on the issue, you would be presented with more information such as where the error happened, the user's Chrome version, OS type, and more, which can provide you with context to help solve the issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Notes
&lt;/h2&gt;

&lt;p&gt;As mentioned briefly, this example assumes your extension has an HTML page, however not all extensions will work like this. In the event that your extension doesn't have an HTML page to output, or if you also need error tracking outside of the scripts referenced in the HTML page, you would need to include Background Scripts that inject the script tag and init Sentry. This would take 2 parts: updating the manifest to reference your new Background Script and adding the initialization into the Background Script itself.&lt;/p&gt;

&lt;p&gt;Another point worth mentioning is the inclusion of the Sentry library via the CDN. This can be avoided if you download the source code for your error tracking tool and bundle that with your extension, however this can come with its own challenges, such as having to ensure that file is kept up to date as new versions are released (which may require you to push an update to the web store for your extension with the latest Sentry code).&lt;/p&gt;

&lt;p&gt;I hope this provided you with a basic introduction to error handling in the Chrome Extension environment so you can be confident in the future that your extensions are running smoothly for all users.&lt;/p&gt;

&lt;p&gt;If you have any questions, feel free to reach out to me on &lt;a href="https://twitter.com/rfitzio" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;. Also, I created &lt;a href="https://ChromeExtensionKit.com?ref=devto" rel="noopener noreferrer"&gt;ChromeExtensionKit&lt;/a&gt;, which is a kit that helps simplify the Chrome extension development and growth process, so feel free to check it out!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>html</category>
      <category>chromeextension</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Tailwind + React: Setup and Design Patterns</title>
      <dc:creator>Ryan Fitzgerald</dc:creator>
      <pubDate>Tue, 20 Oct 2020 02:42:39 +0000</pubDate>
      <link>https://dev.to/rfitz/tailwind-react-setup-and-design-patterns-f7o</link>
      <guid>https://dev.to/rfitz/tailwind-react-setup-and-design-patterns-f7o</guid>
      <description>&lt;p&gt;You can find all source code for this post in a starter template &lt;a href="https://github.com/RyanFitzgerald/react-tailwind-starter" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Feel free to &lt;a href="https://twitter.com/rfitzio" rel="noopener noreferrer"&gt;reach out on Twitter&lt;/a&gt; any time with questions as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Tailwind?
&lt;/h2&gt;

&lt;p&gt;You've probably heard of Tailwind by now, but if you haven't, it is essentially a utility-first CSS framework and is much less opinionated compared to other CSS frameworks like Bootstrap and Foundation. According to &lt;a href="https://tailwindcss.com" rel="noopener noreferrer"&gt;tailwindcss.com&lt;/a&gt;, it is described as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A highly customizable, low-level CSS framework that gives you all of the building blocks you need to build bespoke designs without any annoying opinionated styles you have to fight to override.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my opinion, this makes Tailwind a great option to use for your projects. You get the benefits of rapid development, without the opinionated styles that you ultimately override anyway.&lt;/p&gt;

&lt;p&gt;So what if we want to use Tailwind with React, is it as simple as just including a style sheet? Not exactly. Because of how Tailwind is built and some of the optimizations they have in place (i.e. purging unused CSS), there are a couple extra steps to properly set it up in a React project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the Project Setup
&lt;/h2&gt;

&lt;p&gt;To get started, either jump into your own React project or start up a new one with &lt;code&gt;npx create-react-app tailwind-react-app&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing Dependencies
&lt;/h3&gt;

&lt;p&gt;Next, let's install a couple of dependencies via &lt;code&gt;npm i @fullhuman/postcss-purgecss postcss-cli tailwindcss --save-dev&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A brief explanation of each dependency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fullhuman/postcss-purgecss&lt;/code&gt; - used to purge unused CSS to create the smallest final CSS file possible. If you don't purge unused CSS, tailwind is actually over 2MB uncompressed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;postcss-cli&lt;/code&gt; - Tailwind is a PostCSS plugin. You don't need to use PostCSS as the preprocessor, but in this case we will in order to keep things simple.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tailwindcss&lt;/code&gt; - the Tailwind library and utilities&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create Config Files
&lt;/h3&gt;

&lt;p&gt;Now that we have our dependencies installed, we need to create a couple config files.&lt;/p&gt;

&lt;p&gt;First, create your Tailwind config by running &lt;code&gt;npx tailwind init&lt;/code&gt; in the project root. You can choose to update some of the Tailwind config here if you wish.&lt;/p&gt;

&lt;p&gt;Next, create your PostCSS config by creating a &lt;code&gt;postcss.config.js&lt;/code&gt; file in the project root and adding the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;purgecss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fullhuman/postcss-purgecss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/**/*.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/**/*.jsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./public/**/*.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;defaultExtractor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Za-z0-9-_:&lt;/span&gt;&lt;span class="se"&gt;/]&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tailwindcss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;purgecss&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the first portion, we require and setup &lt;code&gt;purgecss&lt;/code&gt;. This requires two pieces. The first is the list of purgeable files which are js, jsx, and html (you can also add &lt;code&gt;.ts&lt;/code&gt; and &lt;code&gt;.tsx&lt;/code&gt;). The HTML is required so the base styles (e.g. &lt;code&gt;body&lt;/code&gt;, &lt;code&gt;html&lt;/code&gt;, etc) don't get purged.&lt;/p&gt;

&lt;p&gt;The second portion is used to define the extractor function (i.e. rules for which content to keep). PurgeCSS is naive by nature, meaning it will look for content that matches that Regex in the file types listed above. This is important to know and will be revisited when we discuss patterns below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Importing Tailwind CSS
&lt;/h3&gt;

&lt;p&gt;Now that we have our dependencies installed and our config files setup, we can actually add the Tailwind CSS file to our app. To begin, create a &lt;code&gt;tailwind.css&lt;/code&gt; file in the &lt;code&gt;src/&lt;/code&gt; folder with the following Tailwind directives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;utilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will pull in and compile the styles for us automatically when we build our Tailwind output. It's also useful to note that this file is where we could add our own Tailwind custom utilities if we wanted. For more information on adding new utilities, &lt;a href="https://tailwindcss.com/docs/adding-new-utilities#using-css" rel="noopener noreferrer"&gt;click here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, let's import the output file (which we will create via the build scripts in the next section) to our &lt;code&gt;app.js&lt;/code&gt; so it is available globally throughout our app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tailwind.output.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may need to change the path if your &lt;code&gt;app.js&lt;/code&gt; isn't directly in the root of the &lt;code&gt;/src&lt;/code&gt; folder, as that is where this will output by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up Build Scripts
&lt;/h3&gt;

&lt;p&gt;The last step to get Tailwind working is to create a couple quick build scripts to ensure that the output file gets built. Let's add the following to our scripts section in &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"tailwind:build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NODE_ENV=production postcss src/tailwind.css -o src/tailwind.output.css"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"tailwind:dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postcss src/tailwind.css -o src/tailwind.output.css"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"prestart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run tailwind:dev"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"prebuild"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run tailwind:build"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two separate Tailwind scripts we created. The first &lt;code&gt;:build&lt;/code&gt; one is for production builds (i.e. run the &lt;code&gt;PurgeCSS&lt;/code&gt; we defined above), whereas the second one is a dev build which keeps the full Tailwind CSS since we want to access all the styles as we're developing.&lt;/p&gt;

&lt;p&gt;The next part is the &lt;code&gt;prestart&lt;/code&gt; where we run the dev build prior to staring up the React development server. You could take it a step further and add a watch script that checks for changes to &lt;code&gt;tailwind.css&lt;/code&gt;, but I've found that additions / changes to this file are pretty rare, so I simply restart the dev server if this comes up.&lt;/p&gt;

&lt;p&gt;Finally, the last part is the &lt;code&gt;prebuild&lt;/code&gt; script which runs the production (purged) Tailwind build which will result in a CSS file that only contains styles we've actually used in our app.&lt;/p&gt;

&lt;p&gt;That's it! You should now be up and running with Tailwind in your React app. If you're looking to take it a step further, in the next section I'll go over some design patterns and common gotchas when dealing with Tailwind in React.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Patterns
&lt;/h2&gt;

&lt;p&gt;Now that we have Tailwind running within our app, when we can discuss some design patterns that make it easy to work with in React components.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Not To Do
&lt;/h3&gt;

&lt;p&gt;Before jumping into some patterns, I think it's worth mentioning what not to do. How you decide to use Tailwind in your app is ultimately up to your preference, but there is one common mistake that can quickly get you into some trouble and it relates to the purging of the unused CSS as I mentioned above.&lt;/p&gt;

&lt;p&gt;Let's look at an example to illustrate the issue. Assume we have a basic input component as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"border-2 border-gray-300 p-2"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's say we want to conditionally change the border to red if there is an error somewhere. If we have an &lt;code&gt;error&lt;/code&gt; state / prop variable, we may be tempted to do something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
  &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`border-2 border-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red-500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gray-300&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; p-2`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, this looks correct. In fact, it would render the correct classes and work just fine in development. However, once you build the production version of your app, you would notice these styles are missing, but why is that? As mentioned earlier, PurgeCSS runs against our production build to create the smallest Tailwind CSS file possible. It simply looks at the file types you provided and tries to find content matching the Regex. In this case, when it checks this component file, it won't match on &lt;code&gt;border-red-500&lt;/code&gt; or &lt;code&gt;border-gray-300&lt;/code&gt; because they are built dynamically. Instead, it will come across &lt;code&gt;red-500&lt;/code&gt; and &lt;code&gt;gray-300&lt;/code&gt; and not work as intended.&lt;/p&gt;

&lt;p&gt;So how do we avoid this? The easiest way is to always ensure when writing Tailwind classes in your code, the full class name is there and isn't built dynamically. Therefore, using the above example, we get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
  &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`border-2 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;border-red-500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;border-gray-300&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; p-2`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when PurgeCSS runs against this file, it will keep &lt;code&gt;border-red-500&lt;/code&gt; and &lt;code&gt;border-gray-300&lt;/code&gt;, as it should.&lt;/p&gt;

&lt;p&gt;Let's take a look at some basic Tailwind patterns we can use in our app now that we know what not to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Pattern
&lt;/h3&gt;

&lt;p&gt;A pattern I've been using for a little while now is one that involves defining all of your classes above the component in an object with strings for each element.&lt;/p&gt;

&lt;p&gt;For example, here is a basic component:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F78mj2wewas7s7ypnbf5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F78mj2wewas7s7ypnbf5o.png" alt="Basic Component" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using the above pattern, the component code would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;border-2 m-16 p-16&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text-gray-800 text-xl text-center font-bold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BasicComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Basic Component&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This basic pattern has a couple nice benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It keeps the component definition clean, especially if there are a number of elements or if class names get long&lt;/li&gt;
&lt;li&gt;It makes it easy to reuse classes in multiple places. Also, if you need to change styles, you only do it in one place in the component.&lt;/li&gt;
&lt;li&gt;All classes are fully defined in the file, so there are no issues with PurgeCSS&lt;/li&gt;
&lt;li&gt;You get a nice overview of all styles for your component in one neat place&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With that said, what do we do when we get more advanced components that need to change as state or props change in our app? Let's take a look at a more advanced pattern that expands on this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advanced Pattern
&lt;/h3&gt;

&lt;p&gt;Now that we have a basic pattern involving the definition of all our classes above our component, we can take it a step further when we start to have more dynamic components.&lt;/p&gt;

&lt;p&gt;For example, here is a more dynamic component:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbsllmuztmo4m0re1sn6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbsllmuztmo4m0re1sn6.png" alt="Dynamic Component" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the above screenshot, both the button and the subtitle text change when the button is clicked on. Let's look at the code for this component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;border-2 m-16 p-16 text-center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text-gray-800 text-xl font-bold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="s2"&gt;`my-6 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text-red-900 font-medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text-gray-800&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;button&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;py-2 px-4 bg-gray-100 border-2 focus:outline-none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;buttonActive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bg-gray-400&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AdvancedComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setActive&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Advanced Component&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        I change based on the button click state.
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
        &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;clsx&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buttonActive&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setActive&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prevState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;prevState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Click Me
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing to notice is the &lt;code&gt;description&lt;/code&gt; class. Unlike the others, this one is a function that takes in a parameter (or multiple) and outputs a slightly different class (text color and font weight in this case) string as a result. This allows you to maintain the benefits of the basic design pattern, but also add in a conditional aspect as well. It's also import to note, the classes are still fully defined as well so PurgeCSS will still have no issues.&lt;/p&gt;

&lt;p&gt;Another method of creating dynamic classes while maintaining the basic pattern is what was done with the &lt;code&gt;button&lt;/code&gt; and &lt;code&gt;buttonActive&lt;/code&gt; classes. In this case, the &lt;code&gt;clsx&lt;/code&gt; module was used to dynamically add classes based on conditions. The &lt;code&gt;classes.button&lt;/code&gt; class was added without conditions (the base styles), however &lt;code&gt;classes.buttonActive&lt;/code&gt; is only added if &lt;code&gt;active&lt;/code&gt; is true. This method also works well when you have a number of conditionally added classes to an element or if your classes are just getting a little out-of-hand and you want to break them down a bit. Once again, PurgeCSS will work fine with this method as the underlying classes are fully defined.&lt;/p&gt;

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

&lt;p&gt;I hope this helped get you up and running with Tailwind on your React app and gives you some inspiration on how you can use it within your components in a way that makes it easier to work with.&lt;/p&gt;

&lt;p&gt;If you have any questions, suggestions, or run into any issues, feel free to &lt;a href="https://twitter.com/rfitzio" rel="noopener noreferrer"&gt;reach out on Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I open-sourced my portfolio + blog template built with Gatsby &amp; Tailwind</title>
      <dc:creator>Ryan Fitzgerald</dc:creator>
      <pubDate>Sun, 18 Oct 2020 01:59:29 +0000</pubDate>
      <link>https://dev.to/rfitz/i-open-sourced-my-portfolio-blog-template-built-with-gatsby-tailwind-1ak1</link>
      <guid>https://dev.to/rfitz/i-open-sourced-my-portfolio-blog-template-built-with-gatsby-tailwind-1ak1</guid>
      <description>&lt;p&gt;Hey everyone! I recently open-sourced &lt;a href="https://github.com/RyanFitzgerald/devfolio" rel="noopener noreferrer"&gt;Devfolio&lt;/a&gt;, which is a personal portfolio + blog template I built with GatsbyJS and TailwindCSS. &lt;/p&gt;

&lt;p&gt;I wanted to create something that was completely production-ready out of the box (you just edit one config file and deploy), but also easy to customize and extend if you wanted to take it further. Also, because it's built with Gatsby, the final result is fairly fast. &lt;/p&gt;

&lt;p&gt;I'm currently using a slightly customized version for my own &lt;a href="https://rfitz.io/" rel="noopener noreferrer"&gt;personal site&lt;/a&gt; as well, if you wanted to see another example of it.&lt;/p&gt;

&lt;p&gt;Hopefully some of you find it useful for your own purposes and if anyone has any suggestions for additions, I'd love to hear them! Also, if you need any help, feel free to &lt;a href="https://twitter.com/rfitzio" rel="noopener noreferrer"&gt;reach out on Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>opensource</category>
      <category>javascript</category>
      <category>react</category>
    </item>
  </channel>
</rss>
