<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Dermot Hughes]]></title><description><![CDATA[Designer. Developer. Pixel Pusher.]]></description><link>https://dermothughes.com/</link><image><url>https://dermothughes.com/favicon.png</url><title>Dermot Hughes</title><link>https://dermothughes.com/</link></image><generator>Ghost 2.9</generator><lastBuildDate>Mon, 06 Jan 2025 21:35:30 GMT</lastBuildDate><atom:link href="https://dermothughes.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Why Snapshot Testing Sucks: Or, How I Learned to Stop Updating Snapshots and Write Better Tests]]></title><description><![CDATA[A pull request lands in your codebase, and the CI pipeline fails. A snapshot test broke. You open the diff and see ...a handful of seemingly random, tiny changes. You shrug, update the snapshots, and move on. This sucks.]]></description><link>https://dermothughes.com/why-snapshot-testing-sucks/</link><guid isPermaLink="false">Ghost__Post__677c3ddd93ef4304f2229abd</guid><category><![CDATA[Blog]]></category><pubDate>Mon, 06 Jan 2025 20:51:30 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1520623868480-a56b943861ca?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDh8fG1lc3N8ZW58MHx8fHwxNzM2MTk2NDg1fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1520623868480-a56b943861ca?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDh8fG1lc3N8ZW58MHx8fHwxNzM2MTk2NDg1fDA&ixlib=rb-4.0.3&q=80&w=2000" alt="Why Snapshot Testing Sucks: Or, How I Learned to Stop Updating Snapshots and Write Better Tests"/><p>Imagine this: you're deep into developing a new feature. The sprint is almost over, and your team lead reminds you to ensure full test coverage. You know you should write proper unit tests, but you're feeling pressed for time. Instead, you rely on snapshot tests. They're quick, easy, and it's still testing everything, right? Resigned, you update the snapshots, commit the results, and move on, just hoping nothing breaks - even though you know you haven't really tested the code.</p>
<p>Fast forward a week. A pull request lands in your codebase, and the CI pipeline fails. A snapshot test broke. You open the diff and see ...a handful of seemingly random, tiny changes. A stray <code>div</code> appeared. An aria-label was added. Nothing that screams &quot;game-breaking bug.&quot; You shrug, update the snapshots, and move on.</p>
<p>Sound familiar? Let's talk about why this sucks.</p>
<hr>
<h3 id="what-snapshot-testing-promises"><strong>What Snapshot Testing Promises</strong></h3>
<p>At its core, snapshot testing seems brilliant. It's supposed to:</p>
<ol>
<li><strong>Catch unexpected changes</strong>: Ensure your UI or API output doesn't change unexpectedly.</li>
<li><strong>Be easy to implement</strong>: A few lines of code, and boom, your component's output is saved and tested against.</li>
<li><strong>Increase confidence</strong>: Every diff is an opportunity to catch bugs.</li>
</ol>
<p>In theory, it's like having a hawk-eyed reviewer for your code. But in practice? It's more like a pedantic roommate who complains if you move the saltshaker an inch.</p>
<hr>
<h3 id="the-reality-of-snapshot-testing"><strong>The Reality of Snapshot Testing</strong></h3>
<h4 id="1-the-everything-is-fine-updates"><strong>1. The &quot;Everything Is Fine&quot; Updates</strong></h4>
<p>You've added a small feature - say, a new button - and a snapshot test breaks.</p>
<p>&quot;Awesome&quot; you think, &quot;It's working!&quot; You update the snapshot and move on. But did you really check every line of that diff? Do you trust your future self to scrutinize a 200-line snapshot to find the single change you care about?</p>
<p>Let's face it: you probably didn't. Most developers treat snapshot diffs like terms of service agreements - skim and click &quot;I agree.&quot; This makes the test almost meaningless.</p>
<hr>
<h4 id="2-death-by-noise"><strong>2. Death by Noise</strong></h4>
<p>A co-worker changes a margin in a CSS file. Suddenly, 15 snapshots are failing. None of the failures are actual bugs, but now someone has to trudge through every single one and verify they're all &quot;acceptable&quot; changes.</p>
<p>Snapshot tests don't just catch regressions; they also catch intentional changes. In practice, this means:</p>
<ul>
<li>Developers get annoyed and stop trusting tests.</li>
<li>Teams waste time verifying or updating snapshots.</li>
</ul>
<p>Instead of being helpful, the tests turn into a time sink - the equivalent of debugging a fire alarm that blares every time you toast bread.</p>
<hr>
<h4 id="3-the-false-sense-of-security"><strong>3. The False Sense of Security</strong></h4>
<p>Here's the kicker: Snapshot tests often give a <strong>false sense of security.</strong></p>
<p>Imagine your component outputs a massive JSON object. Your snapshot test dutifully captures it all: key-value pairs, nested structures, and even random whitespace. But the bug you're looking for? It's buried deep in the output, invisible amidst the noise.</p>
<p>You think your snapshot test is comprehensive. In reality, it's just <strong>brittle</strong> and <strong>opaque.</strong> What you really need are targeted tests that verify specific behaviors, not a firehose of output.</p>
<hr>
<h3 id="working-in-large-teams-and-monorepos"><strong>Working in Large Teams and Monorepos</strong></h3>
<p>Snapshot testing can be especially problematic in large teams or monorepos.</p>
<ul>
<li>
<p><strong>Knock-On Effects</strong>: A change to a root-level dependency, like a shared component library, can trigger failures across dozens (or hundreds) of snapshot tests. For example, updating a button component to include an additional <code>aria-label</code> for accessibility might result in failed tests throughout the monorepo. These failures often represent minor visual changes, but the effort required to review and update them can grind development to a halt. Similarly, innocuous changes like adding a <code>data-*</code> attribute for analytics or debugging shouldn't cause tests to fail, but with snapshot tests, they often do.</p>
</li>
<li>
<p><strong>Flaky Tests</strong>: Snapshots are brittle, and their propensity to break over minor, intentional updates makes them unreliable. This issue is compounded when using libraries like styled-components, where dynamically generated class names or IDs are frequently part of the output. While it's possible to serialize and stabilize these IDs, you shouldn't need to go to such lengths for basic testing.</p>
</li>
<li>
<p><strong>Coordination Overhead</strong>: In a monorepo, changes often require cross-team collaboration. Imagine a shared dropdown component is updated to support new keyboard navigation. This improvement could inadvertently cause snapshots to fail in unrelated projects, requiring multiple teams to coordinate fixes and updates, further complicating the workflow. This causes friction and frustration across teams, reducing the likelihood of updating the components in the future.</p>
</li>
</ul>
<p>To address these issues, consider replacing snapshot tests with more targeted, behavior-driven tests that focus on specific functionality rather than capturing entire outputs. This reduces noise and makes it easier to manage large-scale changes.</p>
<hr>
<h3 id="how-ui-should-be-tested"><strong>How UI Should Be Tested</strong></h3>
<p>Effective UI testing focuses on replicating how users actually interact with your application. At the heart of this is proper unit testing. Well-written unit tests ensure that your components work as expected in isolation. They target specific behaviors and outputs, making it easier to identify and address bugs without relying on brittle or opaque snapshot tests. These tests provide clarity, are easier to maintain, and help developers confidently refactor code without fear of breaking unrelated functionality.</p>
<p>When designing your tests, think about how a user engages with the application. Focus on key interactions, such as button clicks or form submissions, and verify the outcomes align with expectations. This approach ensures your tests reflect real-world use and provide meaningful coverage.</p>
<p>Once you've established strong unit tests, you can layer on integration and end-to-end tests for broader coverage, ensuring every level of the application behaves cohesively under realistic scenarios.</p>
<h4 id="avoid-overusing-test-ids"><strong>Avoid Overusing Test IDs</strong></h4>
<p>While <code>data-testid</code> attributes can be useful in some cases, relying on them should be a last resort. As outlined in the <a href="https://testing-library.com/docs/guiding-principles">Testing Library Guiding Principles</a>, your tests should resemble how users interact with your software. Users don't see test IDs; they click buttons, fill out forms, and navigate based on visible content.</p>
<p>When <code>data-testid</code> becomes necessary, it's still better than querying DOM structure or CSS class names, which are prone to frequent changes. For a deeper dive into making your UI tests resilient to change, check out <a href="https://kentcdodds.com/blog/making-your-ui-tests-resilient-to-change">Kent C. Dodds' blog post</a>.</p>
<h4 id="best-practices-for-ui-testing"><strong>Best Practices for UI Testing</strong></h4>
<ol>
<li>
<p><strong>Query by Text or Role</strong>: Use selectors that reflect what users see, like button labels or ARIA roles. For example, instead of querying a button by its test ID, use its visible text: <code>screen.getByText('Submit')</code>. Leveraging <code>getByRole</code> is particularly effective for writing accessible code. For instance, <code>screen.getByRole('button', { name: 'Submit' })</code> ensures the element is not only visually correct but also follows accessibility guidelines, which benefits users relying on assistive technologies. However, note that <code>getByRole</code> can sometimes have a performance impact, especially in large DOM trees, as discussed in <a href="https://github.com/testing-library/dom-testing-library/issues/552#issuecomment-625172052">this GitHub issue</a>.</p>
</li>
<li>
<p><strong>Simulate Real User Actions</strong>: Instead of manually triggering events, use tools like Testing Library's <code>fireEvent</code> or <code>userEvent</code> to simulate real-world interactions.</p>
</li>
<li>
<p><strong>Focus on Behavior</strong>: Your tests should validate the functionality, not the implementation. For example, test that submitting a form triggers the correct API call and shows a success message, not that a specific div appears in the DOM.</p>
</li>
</ol>
<h4 id="considerations-for-i18n"><strong>Considerations for i18n</strong></h4>
<p>One challenge with queries like <code>getByText</code> is handling internationalization (i18n). If your application supports multiple languages, visible text queries may fail when running tests in different locales. To mitigate this, consider:</p>
<ul>
<li>Using ARIA roles and attributes as fallback selectors.</li>
<li>Implementing localization-aware utilities to adapt queries based on language.</li>
</ul>
<p>While these approaches add complexity, they help ensure your tests remain robust across diverse use cases.</p>
<hr>
<h3 id="a-better-way"><strong>A Better Way</strong></h3>
<p>Snapshot testing isn't inherently evil, but it's frequently overused and misapplied. To make it more effective and manageable, focus on these impactful strategies:</p>
<ol>
<li>
<p><strong>Test Specific Behaviors</strong>: Focus on what actually matters. Instead of snapshotting an entire component, test specific outputs or behaviors.</p>
<p><strong>Example:</strong> Instead of snapshotting a whole modal, write tests that check if the modal's title and button labels are correct.</p>
</li>
<li>
<p><strong>Keep Snapshots Small</strong>: If you must use snapshot tests, keep them scoped. Don't snapshot a massive DOM tree when you only care about one button.</p>
</li>
<li>
<p><strong>Review Snapshots Carefully</strong>: Treat snapshot diffs like code reviews. If you can't verify a snapshot change quickly, it's probably too big.</p>
</li>
<li>
<p><strong>Use Visual Regression Testing</strong>: For UI-heavy projects, tools like Percy or Chromatic can be better alternatives. They provide visual diffs, which are easier to understand than raw snapshot files.</p>
</li>
</ol>
<hr>
<h3 id="break-the-cycle"><strong>Break the Cycle</strong></h3>
<p>Snapshot testing is like bubble wrap: satisfying at first, but useless when overdone. It promises safety but often delivers clutter and complacency.</p>
<p>The next time you find yourself updating snapshots without a second thought, pause and ask yourself: &quot;Am I actually improving this codebase?&quot; If the answer is no, it's time to rethink your approach.</p>
<p>Testing should make your life easier, not harder. It's okay to admit that snapshot testing sucks - because it often does. By embracing accessible and behavior-driven testing practices, you can improve both your codebase and the user experience. With a little thought and restraint, you can tame snapshot testing and make it work for you, not against you.</p>
<!--kg-card-end: markdown--></hr></hr></hr></hr></hr></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[Unlocking the Power of Compound Components in React]]></title><description><![CDATA[One of the patterns that can significantly improve our codebase is the Compound Component pattern. Today, we're diving deep into how this pattern works, the problems it solves, and some advanced techniques.]]></description><link>https://dermothughes.com/unlocking-the-power-of-compound-components/</link><guid isPermaLink="false">Ghost__Post__66b519910ac5b259be326067</guid><category><![CDATA[Blog]]></category><pubDate>Thu, 08 Aug 2024 19:33:47 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1616121474380-abe0aa516c00?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHJ1c3NpYW4lMjBkb2xsfGVufDB8fHx8MTcyMzE0NDY5Mnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1616121474380-abe0aa516c00?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHJ1c3NpYW4lMjBkb2xsfGVufDB8fHx8MTcyMzE0NDY5Mnww&ixlib=rb-4.0.3&q=80&w=2000" alt="Unlocking the Power of Compound Components in React"/><p>If you've been working with React for a while, you've probably encountered the need for creating reusable and maintainable UI components. One of the patterns that can significantly improve our codebase is the Compound Component pattern. Today, we're diving deep into how this pattern works, the problems it solves, and some advanced techniques to make our components even more powerful.</p><p><em>Before we dive in, a quick note on accessibility: It’s crucial to keep accessibility in mind when building components. While we’ll keep things simple for clarity, always ensure your components are accessible in real-world applications.</em></p><hr><h2 id="why-the-compound-component-pattern">Why the Compound Component Pattern?</h2><h3 id="1-prop-drilling">1. Prop Drilling</h3><p>Imagine you have a parent component that needs to pass data down to a deeply nested child component. To do this, you have to pass the data through every layer in between, which can quickly become messy and hard to manage. This is known as "prop drilling," and it's a common headache in React. The Compound Component pattern helps us avoid this by allowing child components to communicate directly with their parent, without needing to pass props through every intermediate layer.</p><h3 id="2-component-configuration">2. Component Configuration</h3><p>As your components grow more complex, managing configurations through props can become cumbersome. You might find yourself with a component that has a dozen or more props, making it difficult to use and maintain. The Compound Component pattern provides a more intuitive and flexible way to configure components by letting related components "talk" to each other directly.</p><h3 id="3-reusability-and-composability">3. Reusability and Composability</h3><p>This pattern is fantastic for promoting reusability and composability. It lets us build components that fit together like Lego bricks, leading to cleaner and more maintainable code.</p><hr><h2 id="features-of-compound-components">Features of Compound Components</h2><h3 id="1-context-api-usage">1. Context API Usage</h3><p>The first tool in our toolbox is React's <a href="https://react.dev/learn/passing-data-deeply-with-context">Context API.</a> Think of it as a way to create a shared space where components can store and access data, without needing to pass it explicitly through props. This shared space is known as "context," and it helps keep our component hierarchy clean by avoiding prop drilling.</p><h3 id="2-clean-and-intuitive-api">2. Clean and Intuitive API</h3><p>By using compound components, we can expose a clean and intuitive API. This means that other developers (or future you) can easily understand how to use your components without having to dig into the implementation details.</p><h3 id="3-flexibility-in-composition">3. Flexibility in Composition</h3><p>This pattern allows us to define child components within the parent component, giving us flexibility in composition. It makes customizing and extending components a breeze.</p><hr><h2 id="implementing-compound-components-a-practical-example">Implementing Compound Components: A Practical Example</h2><p>Let's kick things off by creating a simple <code>Tabs</code> component using the Compound Component pattern.</p><h3 id="step-1-define-the-context">Step 1: Define the Context</h3><p>First, we need a way for our components to share data without passing props through every level of the component tree. This is where React’s Context API comes in.</p><p>Here’s how it works: We create a context, which is like a global object that can store data. Any component inside this context can access the data without needing props. We’ll also create a provider component (<code>TabsProvider</code>) that wraps our context and provides the data to any child component that needs it.</p><pre><code class="language-jsx">import React, { createContext, useState, useContext } from 'react';

const TabsContext = createContext();

const TabsProvider = ({ children }) =&gt; {
  const [activeTab, setActiveTab] = useState(0);

  const value = {
    activeTab,
    setActiveTab,
  };

  return (
    &lt;TabsContext.Provider value={value}&gt;
      {children}
    &lt;/TabsContext.Provider&gt;
  );
};

const useTabsContext = () =&gt; {
  const context = useContext(TabsContext);
  if (!context) {
    throw new Error('useTabsContext must be used within a TabsProvider');
  }
  return context;
};</code></pre><h3 id="what-did-we-just-do"><strong>What did we just do?</strong></h3><ul><li>We created a <code>TabsContext</code> that will hold the shared state.</li><li>We made a <code>TabsProvider</code> component that wraps its children and gives them access to this context.</li><li>Finally, we wrote a <code>useTabsContext</code> hook, which is just a convenient way to access the context in our components.</li></ul><h3 id="step-2-create-the-compound-components"><br>Step 2: Create the Compound Components</br></h3><p>Now that we have our context set up, let’s create the actual <code>Tabs</code> components. We’ll start with a parent <code>Tabs</code> component, which will use our <code>TabsProvider</code> to wrap everything. Then, we’ll create <code>TabList</code>, <code>Tab</code>, and <code>TabPanel</code> components.</p><pre><code class="language-jsx">const Tabs = ({ children }) =&gt; {
  return &lt;TabsProvider&gt;{children}&lt;/TabsProvider&gt;;
};

const TabList = ({ children }) =&gt; {
  return &lt;div role="tablist" className="tab-list"&gt;{children}&lt;/div&gt;;
};

const Tab = ({ children, index }) =&gt; {
  const { activeTab, setActiveTab } = useTabsContext();

  return (
    &lt;button
      role="tab"
      aria-selected={activeTab === index}
      className={\`tab \${activeTab === index ? 'active' : ''}\`}
      onClick={() =&gt; setActiveTab(index)}
    &gt;
      {children}
    &lt;/button&gt;
  );
};

const TabPanel = ({ children, index }) =&gt; {
  const { activeTab } = useTabsContext();

  return activeTab === index ? (
    &lt;div role="tabpanel" className="tab-panel"&gt;{children}&lt;/div&gt;
  ) : null;
};</code></pre><h3 id="what-did-we-just-do-1"><strong>What did we just do?</strong></h3><ul><li>The <code>Tabs</code> component wraps its children with <code>TabsProvider</code>, giving them access to the context.</li><li>The <code>TabList</code> component serves as a container for the tabs.</li><li>The <code>Tab</code> component uses the context to determine if it’s the active tab and updates the active tab when clicked.</li><li>The <code>TabPanel</code> component displays content only when its corresponding tab is active.</li></ul><p>Notice that we’re using standard HTML roles like <code>tab</code>, <code>tablist</code>, and <code>tabpanel</code> to make our components more accessible. This is a good practice to follow when building real-world applications.</p><h3 id="step-3-compose-the-components">Step 3: Compose the Components</h3><p>Let’s put everything together to create a working <code>Tabs</code> interface.</p><pre><code class="language-jsx">const App = () =&gt; {
  return (
    &lt;Tabs&gt;
      &lt;TabList&gt;
        &lt;Tab index={0}&gt;Tab 1&lt;/Tab&gt;
        &lt;Tab index={1}&gt;Tab 2&lt;/Tab&gt;
        &lt;Tab index={2}&gt;Tab 3&lt;/Tab&gt;
      &lt;/TabList&gt;
      &lt;TabPanel index={0}&gt;Content for Tab 1&lt;/TabPanel&gt;
      &lt;TabPanel index={1}&gt;Content for Tab 2&lt;/TabPanel&gt;
      &lt;TabPanel index={2}&gt;Content for Tab 3&lt;/TabPanel&gt;
    &lt;/Tabs&gt;
  );
};

export default App;</code></pre><h2 id="handling-different-states-conditionally">Handling Different States Conditionally</h2><p>Next, let’s build a file dropzone component that handles different states, like accepting or rejecting a file. This is a great example of using the Compound Component pattern to conditionally render content based on internal state.</p><p>But first, let's introduce a slightly different way of organising our compound components: using dot notation.</p><h3 id="step-1-define-the-context-1">Step 1: Define the Context</h3><p>Just like before, we’ll start by creating a context to manage the dropzone’s state.</p><pre><code class="language-jsx">import React, { createContext, useContext, useState } from 'react';

const DropzoneContext = createContext();

const DropzoneProvider = ({ children, state }) =&gt; {
  const value = { state };
  return &lt;DropzoneContext.Provider value={value}&gt;{children}&lt;/DropzoneContext.Provider&gt;;
};

const useDropzoneContext = () =&gt; {
  const context = useContext(DropzoneContext);
  if (!context) {
    throw new Error('useDropzoneContext must be used within a DropzoneProvider');
  }
  return context;
};</code></pre><h3 id="step-2-create-the-compound-components-with-dot-notation">Step 2: Create the Compound Components with dot notation</h3><p>Here’s where things get interesting. Instead of having separate components like <code>Tab</code> and <code>TabList</code>, we’re going to use dot notation to organize our dropzone components under a single <code>Dropzone</code> namespace. This makes it clear that these components are related and intended to be used together.</p><pre><code class="language-jsx">const Dropzone = ({ children, state }) =&gt; {
  return &lt;DropzoneProvider state={state}&gt;{children}&lt;/DropzoneProvider&gt;;
};

Dropzone.Accept = ({ children }) =&gt; {
  const { state } = useDropzoneContext();
  return state === 'accept' ? &lt;div className="dropzone-accept"&gt;{children}&lt;/div&gt; : null;
};

Dropzone.Reject = ({ children }) =&gt; {
  const { state } = useDropzoneContext();
  return state === 'reject' ? &lt;div className="dropzone-reject"&gt;{children}&lt;/div&gt; : null;
};

Dropzone.Idle = ({ children }) =&gt; {
  const { state } = useDropzoneContext();
  return state === 'idle' ? &lt;div className="dropzone-idle"&gt;{children}&lt;/div&gt; : null;
};

Dropzone.Disabled = ({ children }) =&gt; {
  const { state } = useDropzoneContext();
  return state === 'disabled' ? &lt;div className="dropzone-disabled"&gt;{children}&lt;/div&gt; : null;
};</code></pre><p>Dot notation helps keep related components organized under a single namespace (<code>Dropzone</code>). It makes the API cleaner and avoids potential naming conflicts since all related components are grouped together.</p><p>You only need to import the entire parent component (e.g., <code>Dropzone</code>) to access any of its sub-components, which depending on how you look at it, could be a benefit or a downside. Everything comes with the component without needing to individually import sub-components, but it does increase the bundle size.</p><h3 id="step-3-use-the-dropzone-component">Step 3: Use the Dropzone Component</h3><p>Now let’s see how we can use this <code>Dropzone</code> component in our app.</p><pre><code class="language-jsx">const App = () =&gt; {
  const [dropzoneState, setDropzoneState] = useState('idle');

  return (
    &lt;div&gt;
      &lt;button onClick={() =&gt; setDropzoneState('accept')}&gt;Accept&lt;/button&gt;
      &lt;button onClick={() =&gt; setDropzoneState('reject')}&gt;Reject&lt;/button&gt;
      &lt;button onClick={() =&gt; setDropzoneState('idle')}&gt;Idle&lt;/button&gt;
      &lt;button onClick={() =&gt; setDropzoneState('disabled')}&gt;Disabled&lt;/button&gt;

      &lt;Dropzone state={dropzoneState}&gt;
        &lt;Dropzone.Accept&gt;File accepted! 😊&lt;/Dropzone.Accept&gt;
        &lt;Dropzone.Reject&gt;File rejected! 😢&lt;/Dropzone.Reject&gt;
        &lt;Dropzone.Idle&gt;Drag a file here or click to upload.&lt;/Dropzone.Idle&gt;
        &lt;Dropzone.Disabled&gt;Dropzone is disabled. 🚫&lt;/Dropzone.Disabled&gt;
      &lt;/Dropzone&gt;
    &lt;/div&gt;
  );
};

export default App;</code></pre><h2 id="advanced-techniques-and-tips">Advanced Techniques and Tips</h2><h3 id="1-default-props">1. Default Props</h3><p>Sometimes, you want to provide a sensible default for your components, but still allow users to customize them if they need to. This makes your components more flexible. For example, you can set a default active tab but still let users override it if they want.</p><pre><code class="language-jsx">const Tabs = ({ children, defaultTab = 0 }) =&gt; {
  const [activeTab, setActiveTab] = useState(defaultTab);
  return &lt;TabsProvider value={{ activeTab, setActiveTab }}&gt;{children}&lt;/TabsProvider&gt;;
};</code></pre><h3 id="2-custom-hooks-for-context">2. Custom Hooks for Context</h3><p>Creating custom hooks for context logic can simplify usage and improve readability. Checking for context and returning an error can improve the Developer Experience for users.</p><pre><code class="language-jsx">const useTabs = () =&gt; {
  const context = useContext(TabsContext);
  if (!context) {
    throw new Error('useTabs must be used within a TabsProvider');
  }
  return context;
};</code></pre><h3 id="3-dynamic-component-composition">3. Dynamic Component Composition</h3><p>We can dynamically compose compound components based on certain conditions, such as layout direction.</p><pre><code class="language-jsx">const Tabs = ({ children, vertical = false }) =&gt; {
  return &lt;div className={\`tabs \${vertical ? 'vertical' : 'horizontal'}\`}&gt;{children}&lt;/div&gt;;
};</code></pre><h3 id="4-enhanced-composition-with-render-props">4. Enhanced Composition with Render Props</h3><p>Render props give you a super flexible way to control how your components are rendered. Instead of just passing static content, you can pass a function that returns whatever content you want. This gives you more control over the behavior of your components.</p><pre><code class="language-jsx">const Tabs = ({ children, defaultTab = 0 }) =&gt; {
  const [activeTab, setActiveTab] = useState(defaultTab);
  return (
    &lt;TabsContext.Provider value={{ activeTab, setActiveTab }}&gt;
      {typeof children === 'function' ? children({ activeTab, setActiveTab }) : children}
    &lt;/TabsContext.Provider&gt;
  );
};</code></pre><h3 id="5-typescript-integration">5. TypeScript Integration</h3><p>Integrating type definitions can enhance our development experience.</p><pre><code class="language-tsx">interface TabsContextProps {
  activeTab: number;
  setActiveTab: (index: number) =&gt; void;
}

const TabsContext = createContext&lt;TabsContextProps | undefined&gt;(undefined);</code></pre><h2 id="real-world-example-multi-step-wizard-form">Real-World Example: Multi-Step Wizard Form</h2><p>Let's combine everything we've learned to create a multi-step wizard form.</p><h3 id="step-1-define-the-context-2">Step 1: Define the Context</h3><pre><code class="language-jsx">import React, { createContext, useContext, useState, useEffect } from 'react';

const WizardContext = createContext();

const WizardProvider = ({ children, initialStep = 0 }) =&gt; {
  const [currentStep, setCurrentStep] = useState(initialStep);
  const [isStepValid, setIsStepValid] = useState(true);

  const nextStep = () =&gt; isStepValid &amp;&amp; setCurrentStep((prev) =&gt; prev + 1);
  const prevStep = () =&gt; setCurrentStep((prev) =&gt; Math.max(prev - 1, 0));
  const validateStep = (isValid) =&gt; setIsStepValid(isValid);

  const value = { currentStep, nextStep, prevStep, validateStep };
  return &lt;WizardContext.Provider value={value}&gt;{children}&lt;/WizardContext.Provider&gt;;
};

const useWizardContext = () =&gt; {
  const context = useContext(WizardContext);
  if (!context) {
    throw new Error('useWizardContext must be used within a WizardProvider');
  }
  return context;
};</code></pre><h3 id="step-2-create-the-compound-components-1">Step 2: Create the Compound Components</h3><pre><code class="language-jsx">const Wizard = ({ children, initialStep }) =&gt; {
  return &lt;WizardProvider initialStep={initialStep}&gt;{children}&lt;/WizardProvider&gt;;
};

Wizard.Step = ({ children, stepIndex, validate }) =&gt; {
  const { currentStep, validateStep } = useWizardContext();

  useEffect(() =&gt; {
    if (validate) {
      validateStep(validate());
    }
  }, [currentStep, validate, validateStep]);

  return currentStep === stepIndex ? &lt;div className="wizard-step"&gt;{children}&lt;/div&gt; : null;
};

Wizard.Navigation = () =&gt; {
  const { currentStep, nextStep, prevStep } = useWizardContext();
  return (
    &lt;div className="wizard-navigation"&gt;
      &lt;button onClick={prevStep} disabled={currentStep === 0}&gt;Back&lt;/button&gt;
      &lt;button onClick={nextStep}&gt;Next&lt;/button&gt;
    &lt;/div&gt;
  );
};

Wizard.Summary = ({ children }) =&gt; {
  const { currentStep } = useWizardContext();
  return currentStep === -1 ? &lt;div className="wizard-summary"&gt;{children}&lt;/div&gt; : null;
};</code></pre><h3 id="step-3-use-the-wizard-component">Step 3: Use the Wizard Component</h3><pre><code class="language-jsx">const App = () =&gt; {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    address: ''
  });

  const handleChange = (e) =&gt; {
    const { name, value } = e.target;
    setFormData((prev) =&gt; ({ ...prev, [name]: value }));
  };

  return (
    &lt;Wizard initialStep={0}&gt;
      &lt;Wizard.Step stepIndex={0} validate={() =&gt; formData.name !== ''}&gt;
        &lt;h2&gt;Step 1: Basic Information&lt;/h2&gt;
        &lt;label&gt;
          Name:
          &lt;input type="text" name="name" value={formData.name} onChange={handleChange} /&gt;
        &lt;/label&gt;
      &lt;/Wizard.Step&gt;

      &lt;Wizard.Step stepIndex={1} validate={() =&gt; formData.email !== ''}&gt;
        &lt;h2&gt;Step 2: Contact Information&lt;/h2&gt;
        &lt;label&gt;
          Email:
          &lt;input type="email" name="email" value={formData.email} onChange={handleChange} /&gt;
        &lt;/label&gt;
      &lt;/Wizard.Step&gt;

      &lt;Wizard.Step stepIndex={2} validate={() =&gt; formData.address !== ''}&gt;
        &lt;h2&gt;Step 3: Address Information&lt;/h2&gt;
        &lt;label&gt;
          Address:
          &lt;input type="text" name="address" value={formData.address} onChange={handleChange} /&gt;
        &lt;/label&gt;
      &lt;/Wizard.Step&gt;

      &lt;Wizard.Summary&gt;
        &lt;h2&gt;Summary&lt;/h2&gt;
        &lt;p&gt;Name: {formData.name}&lt;/p&gt;
        &lt;p&gt;Email: {formData.email}&lt;/p&gt;
        &lt;p&gt;Address: {formData.address}&lt;/p&gt;
      &lt;/Wizard.Summary&gt;

      &lt;Wizard.Navigation /&gt;
    &lt;/Wizard&gt;
  );
};

export default App;</code></pre><h2 id="conclusion">Conclusion</h2><p>We've just explored how the Compound Component pattern in React can be a game-changer for building complex, state-driven UIs. By leveraging context and modular sub-components, we can create flexible, maintainable, and intuitive interfaces. This approach ensures clean separation of concerns, easy state management, and dynamic, conditional rendering based on internal state. Whether we're building tabs, dropzones, or multi-step forms, experimenting with these advanced techniques can greatly enhance the functionality and user experience of our React applications. And remember, always keep accessibility in mind!</p></hr></hr></hr>]]></content:encoded></item><item><title><![CDATA[JavaScript Array Methods Cheatsheet]]></title><description><![CDATA[A simple cheatsheet for all the JavaScript Array Methods]]></description><link>https://dermothughes.com/eli5-js-array-methods/</link><guid isPermaLink="false">Ghost__Post__641f64ea0ac5b259be326023</guid><category><![CDATA[Blog]]></category><pubDate>Wed, 31 May 2023 21:17:45 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1581273154768-0a9a16887d2a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE1fHxncmlkfGVufDB8fHx8MTY4NTU2NzgyMnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<h2 id="1-mutator-methods">1. Mutator methods</h2><img src="https://images.unsplash.com/photo-1581273154768-0a9a16887d2a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE1fHxncmlkfGVufDB8fHx8MTY4NTU2NzgyMnww&ixlib=rb-4.0.3&q=80&w=2000" alt="JavaScript Array Methods Cheatsheet"/><p><strong>push()</strong>: Adds one or more elements to the end of an array and returns the new length of the array.</p><pre><code class="language-javascript">let arr = [1, 2, 3];
arr.push(4, 5); // returns 5
// arr is now [1, 2, 3, 4, 5]
</code></pre><p><strong>pop()</strong>: Removes the last element from an array and returns that element.</p><pre><code class="language-javascript">let arr = [1, 2, 3];
let last = arr.pop(); // returns 3
// arr is now [1, 2]
</code></pre><p><strong>shift()</strong>: Removes the first element from an array and returns that element.</p><pre><code class="language-javascript">let arr = ["a", "b", "c"];
let first = arr.shift(); // returns "a"
// arr is now ["b", "c"]
</code></pre><p><strong>unshift()</strong>: Adds one or more elements to the beginning of an array and returns the new length of the array.</p><pre><code class="language-javascript">let arr = [1, 2, 3];
arr.unshift(0); // returns 4
// arr is now [0, 1, 2, 3]
</code></pre><p><strong>splice()</strong>: Changes the contents of an array by removing or replacing existing elements and/or adding new elements in place.</p><pre><code class="language-javascript">let arr = [1, 2, 3, 4, 5];
arr.splice(2, 0, "a", "b"); // returns [], no elements removed
// arr is now [1, 2, "a", "b", 3, 4, 5]
</code></pre><p><strong>reverse()</strong>: Reverses the order of the elements of an array in place.</p><pre><code class="language-javascript">let arr = [1, 2, 3];
arr.reverse(); 
// arr is now [3, 2, 1]
</code></pre><p><strong>sort()</strong>: Sorts the elements of an array in place and returns the array.</p><pre><code class="language-javascript">let arr = [1, 3, 2];
arr.sort(); 
// arr is now [1, 2, 3]
</code></pre><h2 id="2-accessor-methods">2. Accessor methods</h2><p><strong>concat()</strong>: Returns a new array that is this array joined with other array(s) and/or value(s).</p><pre><code class="language-javascript">let arr = [1, 2, 3];
let newArr = arr.concat([4, 5]); 
// newArr is [1, 2, 3, 4, 5]
</code></pre><p><strong>join()</strong>: Joins all elements of an array into a string.</p><pre><code class="language-javascript">let arr = ["Hello", "world"];
let str = arr.join(" "); 
// str is "Hello world"
</code></pre><p><strong>slice()</strong>: Extracts a section of the calling array and returns a new array.</p><pre><code class="language-javascript">let arr = [1, 2, 3, 4, 5];
let newArr = arr.slice(1, 3); 
// newArr is [2, 3]
</code></pre><p><strong>indexOf()</strong>: Returns the first (least) index of an element within the array equal to the specified value, or -1 if none is found.</p><pre><code class="language-javascript">let arr = [1, 2, 3, 4, 5];
let index = arr.indexOf(3); 
// index is 2
</code></pre><p><strong>lastIndexOf()</strong>: Returns the last (greatest) index of an element within the array equal to the specified value, or -1 if none is found.</p><pre><code class="language-javascript">let arr = [1, 2, 3, 2, 

1];
let index = arr.lastIndexOf(2); 
// index is 3
</code></pre><h2 id="3-iteration-methods">3. Iteration methods</h2><p><strong>forEach()</strong>: Executes a provided function once per array element.</p><pre><code class="language-javascript">let arr = [1, 2, 3];
arr.forEach(item =&gt; console.log(item));
// outputs 1, 2, 3
</code></pre><p><strong>map()</strong>: Creates a new array with the results of calling a provided function on every element in this array.</p><pre><code class="language-javascript">let arr = [1, 2, 3];
let newArr = arr.map(item =&gt; item * 2);
// newArr is [2, 4, 6]
</code></pre><p><strong>filter()</strong>: Creates a new array with all elements that pass the test implemented by the provided function.</p><pre><code class="language-javascript">let arr = [1, 2, 3, 4, 5];
let newArr = arr.filter(item =&gt; item &gt; 3);
// newArr is [4, 5]
</code></pre><p><strong>reduce()</strong>: Applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value.</p><pre><code class="language-javascript">let arr = [1, 2, 3, 4, 5];
let sum = arr.reduce((total, value) =&gt; total + value, 0);
// sum is 15
</code></pre><p><strong>some()</strong>: Tests whether some element in the array passes the test implemented by the provided function.</p><pre><code class="language-javascript">let arr = [1, 2, 3, 4, 5];
let hasLargeNumber = arr.some(item =&gt; item &gt; 4);
// hasLargeNumber is true
</code></pre><p><strong>every()</strong>: Tests whether all elements in the array pass the test implemented by the provided function.</p><pre><code class="language-javascript">let arr = [1, 2, 3, 4, 5];
let allGreaterThanZero = arr.every(item =&gt; item &gt; 0);
// allGreaterThanZero is true
</code></pre><p><strong>find()</strong>: Returns the value of the first element in the array that satisfies the provided testing function.</p><pre><code class="language-javascript">let arr = [1, 2, 3, 4, 5];
let firstLargeNumber = arr.find(item =&gt; item &gt; 3);
// firstLargeNumber is 4
</code></pre><p><strong>findIndex()</strong>: Returns the index of the first element in the array that satisfies the provided testing function.</p><pre><code class="language-javascript">let arr = [1, 2, 3, 4, 5];
let firstLargeNumberIndex = arr.findIndex(item =&gt; item &gt; 3);
// firstLargeNumberIndex is 3
</code></pre><p><strong>includes()</strong>: Determines whether an array includes a certain element, returning true or false as appropriate.</p><pre><code class="language-javascript">let arr = [1, 2, 3, 4, 5];
let includesThree = arr.includes(3);
// includesThree is true
</code></pre>]]></content:encoded></item><item><title><![CDATA[Passing at least one optional prop with TypeScript]]></title><description><![CDATA[Setting if a prop is optional or not in TypeScript is really simple. But what if you have several optional props, you don't care which one is passed, but one of them has to? That's where Generics can save the day.]]></description><link>https://dermothughes.com/how-to-check-you-pass-at-least-one-optional-prop-with-typescript/</link><guid isPermaLink="false">Ghost__Post__63a23d3c973c1816d015e5dc</guid><category><![CDATA[Blog]]></category><pubDate>Tue, 20 Dec 2022 23:22:46 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1437276030334-d2faf20c1323?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDR8fGZydWl0JTIwYXBwbGVzJTIwb3Jhbmdlc3xlbnwwfHx8fDE2NzE1NzY5Mjk&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1437276030334-d2faf20c1323?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8c2VhcmNofDR8fGZydWl0JTIwYXBwbGVzJTIwb3Jhbmdlc3xlbnwwfHx8fDE2NzE1NzY5Mjk&ixlib=rb-4.0.3&q=80&w=2000" alt="Passing at least one optional prop with TypeScript"/><p>Setting if a prop is optional or not in TypeScript is really simple. If it's optional, add a question mark after the prop name.</p><!--kg-card-begin: markdown--><pre><code class="language-js">interface Fruit {
  apples: string;
  oranges?: string;
 }
</code></pre>
<!--kg-card-end: markdown--><p>But what if you have several optional props, you don't care which one is passed, but one of them has to? That's where Generics can save the day.</p><!--kg-card-begin: markdown--><pre><code class="language-js">interface FruitOptions {
  apple: string;
  orange: number;
  pear?: boolean;
  banana?: string;
  grape?: string[];
}
</code></pre>
<!--kg-card-end: markdown--><p>In this example, I have two props you have to pass, but three optional ones. You have to give me <code>apple</code> and <code>orange</code>, but I'll be pretty full after that so I don't mind if I get a <code>pear</code>, <code>banana</code>, or <code>grape</code>s.</p><p>Let's use a <a href="https://www.typescriptlang.org/docs/handbook/2/generics.html">Generic</a> help us out.</p><!--kg-card-begin: markdown--><pre><code class="language-js">type AtLeastOneOptionalFruit&lt;T, U = {}, V = {}, W = {}&gt; = T &amp; (U | V | W)
</code></pre>
<!--kg-card-end: markdown--><p>Woah! Yes, it looks daunting, but let's break it down.<br><br>The <code>AtLeastOneOptionalFruit</code> type is a generic type that takes four type parameters: <code>T</code>, <code>U</code>, <code>V</code>, and <code>W</code>.  <code>T</code> simply stands for <code>T</code>ype. The type parameter represents the required props (<code>apple</code> and <code>orange</code>), and the <code>U</code>, <code>V</code>, and <code>W</code> type parameters represent the optional fruit props. It's generally considered <a href="https://wanago.io/2020/02/17/typescript-generics-discussing-naming-conventions/">best practice</a> to just move up the alphabet for consecutive variables.</br></br></p><p>The <code>AtLeastOneOptionalFruit</code> type is defined as a union of the <code>T</code> type and a union of the <code>U</code>, <code>V</code>, and <code>W</code> types, which means that a value of this type must have at least one of the optional fruit props.<br><br>Let's put it together in an example.</br></br></p><!--kg-card-begin: markdown--><pre><code class="language-js">interface FruitOptions {
  apple: string;
  orange: number;
  pear?: boolean;
  banana?: string;
  grape?: string[];
}

type AtLeastOneOptionalFruit&lt;T, U = {}, V = {}, W = {}&gt; = T &amp; (U | V | W);

function example(options: AtLeastOneOptionalFruit&lt;FruitOptions, { pear: boolean }, { banana: string }, { grape: string[] }&gt;) {
  // do something with the options object
}

// These calls are all valid because they pass at least one of the optional fruit props
example({ apple: 'red', orange: 4, pear: true });
example({ apple: 'red', orange: 4, banana: 'yellow' });
example({ apple: 'red', orange: 4, grape: ['white', 'red'] });
example({ apple: 'red', orange: 4, pear: true, banana: 'yellow' });
example({ apple: 'red', orange: 4, pear: true, grape: ['orange', 'yellow'] });

// This call is invalid because it doesn't pass any of the optional fruit props
example({ apple: 'red', orange: 4 });
</code></pre>
<!--kg-card-end: markdown--><p>This might seem a bit arbitrary, but I recently came across a need for exactly this when writing a custom tooltip that required either a label as a string, or a custom trigger entirely. I didn't want to set either of them as required. Either option was fine, but you had to provide one of them.<br><br>The above example isn't very scalable though. What if you want to add more optional props, and don't care which one? Imagine are building a car. We have lots of options to choose from, and many are optional. Do we want heated seats? Cruise control?  </br></br></p><!--kg-card-begin: markdown--><pre><code class="language-js">interface CarOptions {
  make?: string;
  model?: string;
  color?: string;
  engineSize?: number;
  transmission?: 'automatic' | 'manual';
  sunroof?: boolean;
  navigation?: boolean;
  heatedSeats?: boolean;
  cruiseControl?: boolean;
  // etc.
}
</code></pre>
<!--kg-card-end: markdown--><p>We can scale our type to account for any number of options using an index signature.</p><!--kg-card-begin: markdown--><pre><code class="language-js">type AtLeastOneCarOption&lt;T&gt; = { [K in keyof T]?: T[K] } &amp; { [K in keyof T]: T[K] }
</code></pre>
<!--kg-card-end: markdown--><p>The <code>AtLeastOneCarOption</code> type is defined as a type intersection, using the <code>&amp;</code> operator, between the <code>T</code> type (just like before) and an object type that has a index signature. The index signature is defined using the <code>[K in keyof T]</code> syntax (<code>K</code> stands for Key), which means that the object type has a string index signature that can be any of the keys of the <code>T</code> type. The type of the value of the index signature is set to be optional, using the <code>?</code> operator, and is set to be the same as the type of the value of the corresponding key of the <code>T</code> type.</p><p>This means that the <code>AtLeastOneCarOption</code> type is a type that has all of the props of the <code>T</code> type, and at least one of the optional props of the <code>T</code> type.</p><!--kg-card-begin: markdown--><pre><code class="language-js">type AtLeastOneCarOption&lt;T&gt; = { [K in keyof T]?: T[K] } &amp; { [K in keyof T]: T[K] }

function purchaseCar(options: AtLeastOneCarOption&lt;CarOptions&gt;) {
  // do something with the options object
}
</code></pre>
<!--kg-card-end: markdown--><p>The <code>AtLeastOneCarOption</code> type is a special type that combines two things: all of the options that we have for the car, and at least one of the optional options that we have for the car. This means that when you use the <code>AtLeastOneCarOption</code> type, you have to choose at least one of the optional options, but you can also choose any of the other options that you want.</p><p>Looking at the <code>AtLeastOneCarOption</code> type, the index signature is defined using the <code>[K in keyof T]</code> syntax (<code>K</code> standing for Key), which means that the object type has a string index signature that can be any of the keys of the <code>T</code> type (Type, just like before). The type of the value of the index signature is set to be optional, using the <code>?</code> operator, and is set to be the same as the type of the value of the corresponding key of the <code>T</code> type. This means that the <code>AtLeastOneCarOption</code> type is a type that has all of the optional props of the <code>T</code> type, and at least one of the optional props of the <code>T</code> type.</p><p>The <code>AtLeastOneCarOption</code> type is also defined as a type intersection, using the <code>&amp;</code> operator, between the object type with the index signature and the <code>T</code> type. This means that <code>AtLeastOneCarOption</code> is a type that has all of the props of the <code>T</code> type, and at least one of the optional props of the <code>T</code> type.</p><p>This allows the <code>purchaseCar</code> function to accept an object that has any combination of the optional props, as long as at least one of them is present. If the object passed to the <code>purchaseCar</code> function doesn't have any of the optional props, the TypeScript compiler will give an error.</p><!--kg-card-begin: markdown--><pre><code class="language-js">// These calls are also valid because they pass at least one of the optional car options
purchaseCar({ color: 'red' });
purchaseCar({ engineSize: 4.0 });
purchaseCar({ transmission: 'automatic' });
purchaseCar({ color: 'red', engineSize: 4.0 });

// This call is invalid because it doesn't pass any of the optional car options
purchaseCar({});
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Frontier Bus Hire]]></title><description><![CDATA[The aim of this project was to replace an expensive, ancient Yell website with a light, fast, and optimised site. The new site was built using Gatsby, taking advantage of Netlify CMS to fulfil a client request to have an easy way to add new content such as fleet photos.]]></description><link>https://dermothughes.com/frontier-bus/</link><guid isPermaLink="false">Ghost__Post__61e0d64af5fa240f64d93d85</guid><category><![CDATA[Showcase]]></category><pubDate>Fri, 14 Jan 2022 02:09:04 GMT</pubDate><media:content url="https://blog.dermothughes.com/content/images/2022/01/Screenshot-2022-01-14-at-01.55.02.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.dermothughes.com/content/images/2022/01/Screenshot-2022-01-14-at-01.55.02.png" alt="Frontier Bus Hire"/><p><a href="https://www.frontierbushire.co.uk">www.frontierbushire.co.uk</a></p><p>The aim of this project was to replace an expensive, ancient Yell website with a light, fast, and optimised site. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2022/01/screencapture-web-archive-org-web-20181126112432-https-frontierbushire-co-uk-2022-01-14-01_35_37.png" class="kg-image" alt="Frontier Bus Hire" loading="lazy" width="2000" height="1744" srcset="https://blog.dermothughes.com/content/images/size/w600/2022/01/screencapture-web-archive-org-web-20181126112432-https-frontierbushire-co-uk-2022-01-14-01_35_37.png 600w, https://blog.dermothughes.com/content/images/size/w1000/2022/01/screencapture-web-archive-org-web-20181126112432-https-frontierbushire-co-uk-2022-01-14-01_35_37.png 1000w, https://blog.dermothughes.com/content/images/size/w1600/2022/01/screencapture-web-archive-org-web-20181126112432-https-frontierbushire-co-uk-2022-01-14-01_35_37.png 1600w, https://blog.dermothughes.com/content/images/size/w2400/2022/01/screencapture-web-archive-org-web-20181126112432-https-frontierbushire-co-uk-2022-01-14-01_35_37.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>The original site</figcaption></img></figure><p>The new site was built using Gatsby, taking advantage of Netlify CMS to fulfil a client request to have an easy way to add new content such as fleet photos.</p><p>I focused on keeping the web as clean a light as possible as the audience are usually needing one thing: a contact. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2022/01/screencapture-frontierbushire-co-uk-2022-01-14-01_41_15.png" class="kg-image" alt="Frontier Bus Hire" loading="lazy" width="2000" height="2363" srcset="https://blog.dermothughes.com/content/images/size/w600/2022/01/screencapture-frontierbushire-co-uk-2022-01-14-01_41_15.png 600w, https://blog.dermothughes.com/content/images/size/w1000/2022/01/screencapture-frontierbushire-co-uk-2022-01-14-01_41_15.png 1000w, https://blog.dermothughes.com/content/images/size/w1600/2022/01/screencapture-frontierbushire-co-uk-2022-01-14-01_41_15.png 1600w, https://blog.dermothughes.com/content/images/size/w2400/2022/01/screencapture-frontierbushire-co-uk-2022-01-14-01_41_15.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Home page on desktop</figcaption></img></figure><p>The top of the site has a call to action to lead to several contact options including a phone number, email, Facebook Messenger and dynamic form for users to submit. Rather than the previous generic form, this form is designed to capture all the information needed for Frontier to return as quickly as possible with a quote.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2022/01/IMG_1043.PNG" class="kg-image" alt="Frontier Bus Hire" loading="lazy" width="1170" height="2532" srcset="https://blog.dermothughes.com/content/images/size/w600/2022/01/IMG_1043.PNG 600w, https://blog.dermothughes.com/content/images/size/w1000/2022/01/IMG_1043.PNG 1000w, https://blog.dermothughes.com/content/images/2022/01/IMG_1043.PNG 1170w" sizes="(min-width: 720px) 720px"><figcaption>Contact page on mobile</figcaption></img></figure><p>The end result was a great success with a huge increase in quote requests via the form, aided by a very fast website which gets a full 100 score on Lighthouse!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2022/01/Screenshot-2022-01-14-at-01.33.04.png" class="kg-image" alt="Frontier Bus Hire" loading="lazy" width="854" height="294" srcset="https://blog.dermothughes.com/content/images/size/w600/2022/01/Screenshot-2022-01-14-at-01.33.04.png 600w, https://blog.dermothughes.com/content/images/2022/01/Screenshot-2022-01-14-at-01.33.04.png 854w" sizes="(min-width: 720px) 720px"><figcaption>100 across the board on Lighthouse</figcaption></img></figure><p>Another major goal and subsequent success was SEO. The site is the top result when search for "bus hire newry", Frontier's home town.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2022/01/Screenshot-2022-01-14-at-02.06.46.png" class="kg-image" alt="Frontier Bus Hire" loading="lazy" width="1816" height="624" srcset="https://blog.dermothughes.com/content/images/size/w600/2022/01/Screenshot-2022-01-14-at-02.06.46.png 600w, https://blog.dermothughes.com/content/images/size/w1000/2022/01/Screenshot-2022-01-14-at-02.06.46.png 1000w, https://blog.dermothughes.com/content/images/size/w1600/2022/01/Screenshot-2022-01-14-at-02.06.46.png 1600w, https://blog.dermothughes.com/content/images/2022/01/Screenshot-2022-01-14-at-02.06.46.png 1816w" sizes="(min-width: 720px) 720px"><figcaption>Top of Google search</figcaption></img></figure>]]></content:encoded></item><item><title><![CDATA[WTF are Design Tokens? | Belfast JS]]></title><description><![CDATA[I recently did a talk at Belfast JS on an introduction to Design Tokens. You can check out the recording here.]]></description><link>https://dermothughes.com/wtf-are-design-tokens/</link><guid isPermaLink="false">Ghost__Post__61dcc9eef5fa240f64d93d5a</guid><category><![CDATA[Blog]]></category><pubDate>Tue, 11 Jan 2022 00:09:25 GMT</pubDate><media:content url="https://blog.dermothughes.com/content/images/2022/01/Screenshot-2022-01-11-at-00.12.50.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.dermothughes.com/content/images/2022/01/Screenshot-2022-01-11-at-00.12.50.png" alt="WTF are Design Tokens? | Belfast JS"/><p>I recently did a talk at Belfast JS on an introduction to Design Tokens. You can check it out below:</p><!--kg-card-begin: html--><p>
<div class="video-container">
    <iframe style="width: 100%;height: 100%;" src="https://www.youtube.com/embed/z8hVnvN0j5s" allowfullscreen=""/>
</div>
</p><!--kg-card-end: html--><p/><p>As a side-note, I used <a href="https://github.com/jxnblk/mdx-deck">MDX Deck</a> to build my deck. It was interesting to build a deck in React although probably overkill for most situations.</p>]]></content:encoded></item><item><title><![CDATA[A neat dynamic glow effect for images]]></title><description><![CDATA[This will be a quick one. I've wondered for a while how Spotify do their dynamic colours from an album cover, so went down a rabbit hole. While I might look into a more accurate way , I discovered a neat trick for getting that dynamic glow effect on an image.]]></description><link>https://dermothughes.com/a-neat-dynamic-glow-effect-for-images/</link><guid isPermaLink="false">Ghost__Post__60a44994fceebf7b0f5bfacc</guid><category><![CDATA[Blog]]></category><pubDate>Tue, 18 May 2021 23:19:42 GMT</pubDate><media:content url="https://blog.dermothughes.com/content/images/2021/05/Screenshot-2021-05-19-at-00.24.17.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.dermothughes.com/content/images/2021/05/Screenshot-2021-05-19-at-00.24.17.png" alt="A neat dynamic glow effect for images"/><p>This will be a quick one. I've wondered for a while how Spotify do their dynamic colours from an album cover, so went down a rabbit hole. While I might look into a more <a href="https://www.npmjs.com/package/react-palette">accurate </a><a href="https://github.com/Vibrant-Colors/node-vibrant">way</a> , I discovered a neat trick for getting that dynamic glow effect on an image.</p><p>Simply stack an image on top of a second copy of that image with some absolute positioning, and blur the image underneath! Pretty fun effect. Just don't forget to hide your "blurred" image with an <code>aria-hidden="true"</code> so screen readers don't call it out unnecessarily.</p><figure class="kg-card kg-embed-card"><iframe id="cp_embed_bGqVOEe" src="https://codepen.io/dermyhughes/embed/preview/bGqVOEe?default-tabs=html%2Cresult&amp;height=300&amp;host=https%3A%2F%2Fcodepen.io&amp;slug-hash=bGqVOEe" title="Neat dynamic glow image effect" scrolling="no" frameborder="0" height="300" allowtransparency="true" class="cp_embed_iframe" style="width: 100%; overflow: hidden;"/></figure>]]></content:encoded></item><item><title><![CDATA[Is the title Front End Developer  obsolete?]]></title><description><![CDATA[There has been an increasing issue where "Front End" just means anything to do with the browser or web. The shear amount of knowledge a Front End Developer apparently needed to know is so vast as to be impossible.]]></description><link>https://dermothughes.com/is-the-title-front-end-developer-dead/</link><guid isPermaLink="false">Ghost__Post__5f1075d479301a0c1c07eb09</guid><category><![CDATA[Blog]]></category><pubDate>Thu, 16 Jul 2020 16:57:12 GMT</pubDate><media:content url="https://blog.dermothughes.com/content/images/2020/07/michelle-chiu-kXPXdY3-8Hc-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.dermothughes.com/content/images/2020/07/michelle-chiu-kXPXdY3-8Hc-unsplash.jpg" alt="Is the title Front End Developer  obsolete?"/><p>Edit: Since writing this, Brad Frost (of Atomic Design fame) published <a href="https://bradfrost.com/blog/post/front-of-the-front-end-and-back-of-the-front-end-web-development/">a fantastic article on this topic</a>. He says it better than I ever could. </p><p>I recently had an interviewer ask me a question about authorisation security with an application I had worked on. I described it something like so:</p><!--kg-card-begin: markdown--><ul>
<li>JWT auth token is requested using the users credentials to the back end.</li>
<li>The Angular Authentication Service checks the token.</li>
<li>If a valid token isn't returned we don't let the user in.</li>
<li>If the token is valid, we let them in.</li>
</ul>
<!--kg-card-end: markdown--><p>The interviewer didn't seem content and pressed me for more detail about how the Auth service works "under the hood". I replied simply, "I don't really know how it works under the hood, I don't need to as long as it works." Was it the wisest answer in an interview? Probably not, but my point stands. I could have gone into more detail about RxJS Subjects and Observables storing the user object and notifying components or how the token is checked.</p><p>I'm not arguing that a good fundamental knowledge isn't important. I think you should learn the basics of JavaScript before you diving straight into a framework like React or Angular. However the frameworks are there for a reason. They abstract away a lot of complicated, repeatable issues developers encounter.</p><p>The fact is I personally don't need to know how it works under the hood. The engineers at Google who wrote it are going to have a much greater understanding of it than I ever will, I frankly I'm not that interested in how it works. I'd rather implement it as Angular recommends, and go back to more interesting tasks.</p><p>There has been an increasing issue where "Front End" just means anything to do with the browser or web. The shear amount of knowledge a Front End Developer apparently needed to know is so vast as to be impossible. Off the top of my head I can list:</p><!--kg-card-begin: markdown--><ul>
<li>Semantic markup</li>
<li>CSS</li>
<li>CSS preprocessors</li>
<li>JavaScript</li>
<li>Cross browser support</li>
<li>APIs</li>
<li>Whatever JS framework is the current flavour of the month</li>
<li>Content Managment</li>
<li>Redux/Reactive Programming</li>
<li>Responsive Design</li>
<li>Accessibility</li>
<li>Internationalisation and localisation</li>
<li>Git</li>
<li>Unit Testing</li>
<li>E2E testing</li>
<li>Performance</li>
<li>Templating</li>
<li>HTML Canvas</li>
<li>Animation</li>
<li>SVG</li>
</ul>
<!--kg-card-end: markdown--><p>Front End Development spans two very different types of work - Design and Software Engineering. Chris Coyier of CSS Tricks speaks about this in his excellent article <a href="https://css-tricks.com/the-great-divide/">The Great Divide</a>.</p><!--kg-card-begin: markdown--><blockquote>
<p>The divide is between people who self-identify as a (or have the job title of) front-end developer, yet have divergent skill sets.</p>
</blockquote>
<blockquote>
<p>On one side, an army of developers whose interests, responsibilities, and skill sets are heavily revolved around JavaScript.</p>
</blockquote>
<blockquote>
<p>On the other, an army of developers whose interests, responsibilities, and skill sets are focused on other areas of the front end, like HTML, CSS, design, interaction, patterns, accessibility, etc.</p>
</blockquote>
<!--kg-card-end: markdown--><p>He raises the point that the term "Front End Developer" is so broad as to be useless.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/07/image.png" class="kg-image" alt="Is the title Front End Developer  obsolete?" loading="lazy"><figcaption>These are the same role?</figcaption></img></figure><p>In a comment on a different post on the topic, the user Steven David commented </p><!--kg-card-begin: markdown--><blockquote>
<p>Most people are not amazing at both JavaScript and CSS. Let UX Engineers work closely with UX/Design to create great designs, interactions, prototypes, etc. and let JavaScript Engineers handle all the data parts.<br>
So sick of being great at CSS but being forced into JavaScript. I’m not a programmer!</br></p>
</blockquote>
<!--kg-card-end: markdown--><p>Likewise, there are many developers who coming from a more Computer Science background, who are very comfortable in JavaScript, but would struggle with the most basic of CSS - instead relying on computationally expensive JavaScript. Or important issues like responsiveness, speed, and accessibility never being a consideration.</p><p>Many developers don't want to touch databases directly, which is fine, so we separate that out to Database Architects. As JavaScript becomes ever more popular, we have Front End developers needing to understand and practice architectural principles that were traditionally in the domain of back-end developers, such as API design and data modelling. This isn't even getting into the stress that is keeping up with new technologies and frameworks under the fear of your skillset getting stale.</p><p>So why do we group all this under one title?</p><p>A few companies are starting to recognise this issue. Google now has a UI Engineer role which sits closer with designers, earlier in the process. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/07/image-1.png" class="kg-image" alt="Is the title Front End Developer  obsolete?" loading="lazy"><figcaption>UX Engineering role at Google&nbsp;</figcaption></img></figure><p>This harks back to my point in <a href="https://dermothughes.com/blog/sympathy-for-the-developer/">my first ever blog post</a> where I raised the point that having someone with developer experience can help catch or solve issues much earlier in the design process, which is a lot cheaper than waiting until it lands on a engineers desk in the middle of a sprint.</p><p>Maybe it's time we look at the term "Front End Developer" and have a think about what that really means.</p><!--kg-card-begin: html--><p>
<div class="video-container">
    <iframe style="width: 100%;height: 100%;" src="https://www.youtube.com/embed/lFOfQsi5ye0" allowfullscreen=""/>
</div>
</p><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[How to generate a PDF from a webpage with Node]]></title><description><![CDATA[How I generate a printable PDF version of my CV automatically every time I update it, using Node and a headless Chrome.]]></description><link>https://dermothughes.com/how-to-generate-a-pdf-from-a-webpage/</link><guid isPermaLink="false">Ghost__Post__5ef468b279301a0c1c07e9ed</guid><category><![CDATA[Blog]]></category><pubDate>Thu, 25 Jun 2020 10:14:10 GMT</pubDate><media:content url="https://blog.dermothughes.com/content/images/2020/06/cv-cover.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.dermothughes.com/content/images/2020/06/cv-cover.jpg" alt="How to generate a PDF from a webpage with Node"/><p>The standard method of showing job history and experience is, of course, the Curriculum Vitae, so naturally I've included mine of this site. I've had my CV written in HTML and CSS for several years now. Originally it was an example of my skills when I had little else as far as a portfolio went, but I've retained it because a responsive CV works much better for displaying on a website.</p><p>However, typically any recruiter or prospective employer wants your CV in a standard, printable format - PDF. You don't want to have to manage two separate versions of your CV every time you make a change so how do you create a PDF version of your web CV?</p><p>First of all, you need a printable version of your CV independent of any other layout, such as the header of the site. In my case, I have the CV as its own React component, which means I only need to edit it in one place, and the cv page on the site consumes this component.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/image.png" class="kg-image" alt="How to generate a PDF from a webpage with Node" loading="lazy"><figcaption>The web CV as displayed on the site</figcaption></img></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/image-1.png" class="kg-image" alt="How to generate a PDF from a webpage with Node" loading="lazy"><figcaption>The web CV on it's own</figcaption></img></figure><p>Once you can view the CV all on its own, you need to make sure it will actually look good when you print it. That's where the CSS <code>@media print</code> media query comes in. If you're not familiar, it is just like the <code>@media screen</code> media query you use for responsive design, but this is specifically for print. Here you can tweak your design until you're happy. </p><blockquote>Protip: Use the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/break-after">break-after</a> property to create natural page breaks</blockquote><p>Don't get too attached as you'll likely need to return to this later once you see how the PDF is generated.</p><hr><p>So how do we convert this into a PDF? That's where <a href="https://github.com/puppeteer/puppeteer">Puppeteer</a> comes in. This API allows us to control a headless Chrome instance using Node.</p><p>First of all, let's install it.</p><p><code>npm i puppeteer --save-dev</code></p><p>We then need to create our service which will generate the PDF. I placed mine at <code>utils/generate-pdf.js</code> but you may have somewhere else for your scripts.</p><pre><code class="language-javascript">const puppeteer = require(`puppeteer`);

(async () =&gt; {
    const browser = await puppeteer.launch();
    let page = await browser.newPage();
    await page.goto(`localhost:9000/mycv`); // URL of the document
    await page.pdf({
        path: `./my-cv.pdf`, // path to save the PDF to.
        format: `A4`, // Page format
    });
    await browser.close();
    console.log(`CV pdf generated.`);
})();</code></pre><p>Modify the URL to point to your own document, and change the path and file name to suit you.</p><p>This script opens Chrome browser in the background, goes to your document, prints a PDF copy, and closes the browser. As these are asynchronous actions, we have an <code>await</code> before them. Simple enough.</p><p>Now all we need to do is add this under <code>scripts</code> in our <code>package.json</code> file.</p><pre><code class="language-json">"generate-cv": "node generate-pdf.js",</code></pre><p>Once you've changed the path to where you've saved your own script, you can run it via the terminal with <code>npm run generate-cv</code>. Ensure your web document is up and running!</p><p>That's it! Run the script and check the generated file to make sure all looks as expected, tweaking your styles as needed. You can add the terminal command to your build process so it will generate a new PDF with each build, so you know it'll always be up-to-date with your current web document. Now it's just a matter of adding a link to that file on your site.<br><br>Edit in 2023:<br><br>Although it's not too difficult to just run this script manually when you need to, wouldn't it be nice to automate it?<br><br>You could run the script as part of the CI pipeline, however that means spinning up a headless Chromium on your CI machine and on something like Netlify that's not as easy as it sounds.</br></br></br></br></br></br></p><p>Another option is to use a tool like <a href="https://typicode.github.io/husky/">Husky</a>. This is often used for running things like linters when you commit, but can also be used to run our PDF script.<br><br>It's nice and easy to install.</br></br></p><pre><code class="language-bash">npx husky-init &amp;&amp; npm install</code></pre><p>It will:</p><ol><li>Add <code>prepare</code> script to <code>package.json</code></li><li>Create a sample <code>pre-commit</code> hook that you can edit (by default, <code>npm test</code> will run when you commit)</li><li>Configure Git hooks path<br/></li></ol><p>We could just get Husky to run our script on every commit, but that would makes things very slow. We only need it to run if we actually change the CV. We can utilise <code>git diff</code> in a conditional for this.</p><pre><code class="language-bash">npx husky add .husky/pre-commit "if ! git diff --staged --quiet -- 'src/components/common/cvRaw.jsx'; then npm run generate-pdf; fi"</code></pre></hr>]]></content:encoded></item><item><title><![CDATA[Royal Bank of Scotland & NatWest]]></title><description><![CDATA[As part of a rebranding update, Royal Bank of Scotland and NatWest required an update to their MyRewards loyalty scheme website. The brief required updating the styles of the site to a fresher and cleaner theme but without changing the core legacy code.]]></description><link>https://dermothughes.com/royal-bank-of-scotland-natwest/</link><guid isPermaLink="false">Ghost__Post__5ef2220279301a0c1c07e967</guid><category><![CDATA[Showcase]]></category><pubDate>Wed, 24 Jun 2020 15:30:57 GMT</pubDate><media:content url="https://blog.dermothughes.com/content/images/2020/06/cover.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.dermothughes.com/content/images/2020/06/cover.jpg" alt="Royal Bank of Scotland & NatWest"/><p>As part of a rebranding update, <a href="https://myrewards.rbs.co.uk/">Royal Bank of Scotland</a> and <a href="https://myrewards.natwest.com/">NatWest</a> required an update to their MyRewards loyalty scheme website. The brief required updating the styles of the site to a fresher and cleaner theme but without changing the core legacy code.</p><p>Therefore everything needed to be done purely with CSS. Screenshots of the final work - <strong>the old design on the left and the updated on the right. </strong></p><p>Any differences to content such as offers, names etc. are purely due to different test accounts used.</p><hr><!--kg-card-begin: markdown--><h3 id="rbs">RBS</h3>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/rbs1.png" class="kg-image" alt="Royal Bank of Scotland & NatWest" loading="lazy"><figcaption>Login page</figcaption></img></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/rbs2.png" class="kg-image" alt="Royal Bank of Scotland & NatWest" loading="lazy"><figcaption>Main offers</figcaption></img></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/rbs3.png" class="kg-image" alt="Royal Bank of Scotland & NatWest" loading="lazy"><figcaption>FAQ's</figcaption></img></figure><hr><!--kg-card-begin: markdown--><h3 id="natwest">Natwest</h3>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/nw1.png" class="kg-image" alt="Royal Bank of Scotland & NatWest" loading="lazy"><figcaption>Login page</figcaption></img></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/nw2.png" class="kg-image" alt="Royal Bank of Scotland & NatWest" loading="lazy"><figcaption>Main offers</figcaption></img></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/nw3.png" class="kg-image" alt="Royal Bank of Scotland & NatWest" loading="lazy"><figcaption>FAQ's</figcaption></img></figure></hr></hr>]]></content:encoded></item><item><title><![CDATA[Reward]]></title><description><![CDATA[During my time at Reward, one of my projects was building a new website for the company. Although most of my work there was on internal apps based on Angular, this brief called for a public facing website that could be edited by the marketing team without need of the development team to intervene.]]></description><link>https://dermothughes.com/reward-insight/</link><guid isPermaLink="false">Ghost__Post__5ef21e6379301a0c1c07e91c</guid><category><![CDATA[Showcase]]></category><pubDate>Tue, 23 Jun 2020 15:35:11 GMT</pubDate><media:content url="https://blog.dermothughes.com/content/images/2020/06/reward-cover.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.dermothughes.com/content/images/2020/06/reward-cover.jpg" alt="Reward"/><p>During my time at <a href="https://www.rewardinsight.com/">Reward</a>, one of my projects was building a new website for the company. Although most of my work there was on internal apps based on Angular, this brief called for a public facing website that could be edited by the marketing team without need for the development team to intervene.</p><p>The decision was made to build the site on WordPress, using the DIVI builder plugin for easier modification by the marketing team for news, updates, and job postings.</p><hr><figure class="kg-card kg-image-card"><img src="https://blog.dermothughes.com/content/images/2020/06/mobile1.png" class="kg-image" alt="Reward" loading="lazy"/></figure><figure class="kg-card kg-image-card"><img src="https://blog.dermothughes.com/content/images/2020/06/mobile2.png" class="kg-image" alt="Reward" loading="lazy"/></figure><figure class="kg-card kg-image-card"><img src="https://blog.dermothughes.com/content/images/2020/06/screencapture-rewardinsight-2019-08-13-09_55_04.png" class="kg-image" alt="Reward" loading="lazy"/></figure><figure class="kg-card kg-image-card"><img src="https://blog.dermothughes.com/content/images/2020/06/screencapture-rewardinsight-retailers-2019-08-13-09_55_47.png" class="kg-image" alt="Reward" loading="lazy"/></figure><figure class="kg-card kg-image-card"><img src="https://blog.dermothughes.com/content/images/2020/06/screencapture-rewardinsight-banks-2019-08-13-09_56_02.png" class="kg-image" alt="Reward" loading="lazy"/></figure><figure class="kg-card kg-image-card"><img src="https://blog.dermothughes.com/content/images/2020/06/screencapture-rewardinsight-careers-2019-08-13-09_56_26.png" class="kg-image" alt="Reward" loading="lazy"/></figure><figure class="kg-card kg-image-card"><img src="https://blog.dermothughes.com/content/images/2020/06/screencapture-rewardinsight-get-in-touch-2019-08-13-09_56_35.png" class="kg-image" alt="Reward" loading="lazy"/></figure></hr>]]></content:encoded></item><item><title><![CDATA[Chain Reaction Cycles]]></title><description><![CDATA[During my time at Chain Reaction Cycles, I was the Lead Email Developer - I worked closely with other designers and marketers to create hundreds of bespoke marketing emails for across the globe in 7 different languages.]]></description><link>https://dermothughes.com/chain-reaction-cycles/</link><guid isPermaLink="false">Ghost__Post__5ef0dbe079301a0c1c07e8da</guid><category><![CDATA[Showcase]]></category><pubDate>Tue, 23 Jun 2020 10:30:30 GMT</pubDate><media:content url="https://blog.dermothughes.com/content/images/2020/06/email-cover.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.dermothughes.com/content/images/2020/06/email-cover.jpg" alt="Chain Reaction Cycles"/><p>I was the Lead Email Developer at <a href="https://www.chainreactioncycles.com/">Chain Reaction Cycles</a>, the largest online bike store in the world. I worked closely with other designers and marketers to create hundreds of bespoke marketing emails for across the globe in 7 different languages. These emails had to be supported over a large variation of email clients across the world.</p><p>During my time at CRC, the scale of sending went from 2-3 a week to 7-8 a week. Each email had over a dozen English variations for different regions, and 7 other languages. To achieve this I had to completely reorganise how emails were built and create a framework to automate tasks and massively increase the turnaround of a design from brief to send.</p><p>It was doing this work that I got an interest for split-testing different designs and researching audience analytics.</p><p>I was also responsible for landing pages for events and product launches.</p><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/email-collage.jpg" class="kg-image" alt="Chain Reaction Cycles" loading="lazy"><figcaption>A small selection of the emails I worked on. These were usually daily, in multiple languages.</figcaption></img></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/crc_landing.png" class="kg-image" alt="Chain Reaction Cycles" loading="lazy"><figcaption>A landing page created to launch new tyres</figcaption></img></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/screencapture-file-Users-dermothughes-Dropbox-showcase-CRC-SeaOtter-html-2020-06-22-17_25_07.png" class="kg-image" alt="Chain Reaction Cycles" loading="lazy"><figcaption>A simple form page for collecting customer details at an event. This page was displayed on iPads at a stand for a competition.</figcaption></img></figure></hr>]]></content:encoded></item><item><title><![CDATA[SeeMeHired]]></title><description><![CDATA[This was a landing page and email template created for the digital recruitment platform, SeeMeHired.]]></description><link>https://dermothughes.com/seemehired/</link><guid isPermaLink="false">Ghost__Post__5ef0d87379301a0c1c07e89b</guid><category><![CDATA[Showcase]]></category><pubDate>Mon, 22 Jun 2020 16:22:47 GMT</pubDate><media:content url="https://blog.dermothughes.com/content/images/2020/06/cover.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.dermothughes.com/content/images/2020/06/cover.png" alt="SeeMeHired"/><p>This was a landing page and email template created for the digital recruitment platform, <a href="https://seemehired.com/">SeeMeHired.</a></p><p>The intention was a landing page that marketing material would link to give a quick overview of what the company was and why people should join it.</p><p>This project included designs and mockups of landing page and email template, and actual build of both.</p><hr><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/desktop_mock.png" class="kg-image" alt="SeeMeHired" loading="lazy"><figcaption>Initial mockup based on website designs. Placeholder hero image is used here.</figcaption></img></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/mobile.png" class="kg-image" alt="SeeMeHired" loading="lazy"><figcaption>Two designs for the mobile view, experimenting with the placement of the video.</figcaption></img></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/cards_Actual.png" class="kg-image" alt="SeeMeHired" loading="lazy"><figcaption>Actual implementation of the cards, logo wall, and bottom call to action.</figcaption></img></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/header_ideas.png" class="kg-image" alt="SeeMeHired" loading="lazy"><figcaption>Different iterations of potential designs for the mobile header regarding placement of the app store buttons.</figcaption></img></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/email.png" class="kg-image" alt="SeeMeHired" loading="lazy"><figcaption>First draft of working email template with consideration for limits of email template design</figcaption></img></figure></hr>]]></content:encoded></item><item><title><![CDATA[Band Artwork]]></title><description><![CDATA[I've played in several bands over the years on bass guitar. Being in a band is great experience for more than just playing music. There's writing, marketing, merchandise, photography, videography, live audio engineering, studio audio engineering, teamwork, public speaking, and so much more.]]></description><link>https://dermothughes.com/band-artwork/</link><guid isPermaLink="false">Ghost__Post__5ed986fac8d6ae0dd8177889</guid><category><![CDATA[Showcase]]></category><pubDate>Fri, 05 Jun 2020 00:01:49 GMT</pubDate><media:content url="https://blog.dermothughes.com/content/images/2020/06/EPcrop.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.dermothughes.com/content/images/2020/06/EPcrop.png" alt="Band Artwork"/><p>I've played in several bands over the years on bass guitar. Being in a band is great experience for more than just playing music. There's writing, marketing, merchandise, photography, videography, live audio engineering, studio audio engineering, teamwork, public speaking, management, and so much more.</p><p>Of course, a lot of that work is a learning experience without fruit to bear - or at least fruit to pick. However, I am ocassionally proud of what I've produced. You'll have to go digging if you want to hear the music though...</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/N2GBH_Cover.png" class="kg-image" alt="Band Artwork" loading="lazy"><figcaption>Front cover of The Shout EP. This was also me experimenting with Glitch Art.</figcaption></img></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/CD-Artwork.png" class="kg-image" alt="Band Artwork" loading="lazy"><figcaption>Back cover of The Shout EP. Your choice if you want to see if those links still work.</figcaption></img></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/shoutposter.jpg" class="kg-image" alt="Band Artwork" loading="lazy"><figcaption>Promotional image to promote new style and lineup. Even though I'm in it, I did shoot the photo (Tripod and remote).</figcaption></img></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/06/COVERTWO.jpg" class="kg-image" alt="Band Artwork" loading="lazy"><figcaption>This is the cover of the first EP I ever recorded (around 2009). It's probably the first thing I ever designed that was professionally mass printed.</figcaption></img></figure>]]></content:encoded></item><item><title><![CDATA[How to display a random cute animal on a 404 page]]></title><description><![CDATA[Hitting a 404 is usually an unpleasant experience so I'll try and balance that out with a pleasant one - and what does everyone love? Cute animals of course!]]></description><link>https://dermothughes.com/displaying-a-random-404-image/</link><guid isPermaLink="false">Ghost__Post__5e87aa1fc8d6ae0dd81776ae</guid><category><![CDATA[Blog]]></category><pubDate>Fri, 03 Apr 2020 22:41:50 GMT</pubDate><media:content url="https://blog.dermothughes.com/content/images/2020/04/custom-404.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.dermothughes.com/content/images/2020/04/custom-404.png" alt="How to display a random cute animal on a 404 page"/><p>As I slowly start piecing together this site, there are plenty of broken links floating about. After the homepage itself, the page I'm seeing the most is the <a href="https://dermothughes.com/404">Error 404 Page Not Found</a> page.</p><p>I hope by the time you're reading this, it will be a much less frequent occurrence but I thought I'd follow a <a href="https://www.creativebloq.com/web-design/best-404-pages-812505">long tradition of custom 404 pages</a> and add my own. Hitting a 404 is usually an unpleasant experience so I'll try and balance that out with a pleasant one - and what does everyone love? Cute animals of course!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.dermothughes.com/content/images/2020/04/scott-webb-ttCFWHrmWH4-unsplash.jpg" class="kg-image" alt="How to display a random cute animal on a 404 page" loading="lazy"><figcaption>Nawww....look at them!</figcaption></img></figure><p>So why not load a random picture of a cute animal when someone hits a 404 Error? This site is built with Gatsby which makes creating a custom 404 page very straightforward - it just loads whatever is at <code>src/pages/404.js</code>. Your page may deal with it differently. The good thing is regardless of what's made to build your page, as long as it can hit the internet (and all the best websites can) getting these random images is super simple.</p><p>We're going to use the ever amazing <a href="https://unsplash.com/">Unsplash</a> for our images. Why? Because they're free! More specifically we're going to use their API <a href="https://source.unsplash.com/">Unsplash Source</a>. They have a more advanced API available, but we don't need it for this.</p><figure class="kg-card kg-image-card"><img src="https://blog.dermothughes.com/content/images/2020/04/Screenshot-2020-04-03-at-23.10.42.png" class="kg-image" alt="How to display a random cute animal on a 404 page" loading="lazy"/></figure><p>If we wanted to get any random image we can simply visit <a href="https://source.unsplash.com/random">https://source.unsplash.com/random</a>.  We don't want any random image though - we want our cute animals! Fortunately Unsplash makes this easy for us.</p><blockquote><a href="https://source.unsplash.com/640x480?cute-animals">https://source.unsplash.com/640x480?cute-animals</a></blockquote><p>You can see our keyword on the end of that link. You can add multiple terms if you want by separating with a comma, but we'll keep it simple. We don't need to give our visitors a huge full resolution file for a little fun like this, so we should add some height and width dimensions so the Unsplash CDN gives us a nice manageable size.</p><p>We're nearly there. All we need to do is add this link where we would normally add a link to an image. This can be with a CSS background image:</p><blockquote><code>background-image: url("https://source.unsplash.com/640x480?cute-animals");</code></blockquote><p>A good old HTML <code>img</code> tag:</p><blockquote><code>&lt;img src="https://source.unsplash.com/640x480?cute-animals"&gt;&lt;/img&gt;</code></blockquote><p>Or however else you want to consume the URL! <strong>Don't forget the purpose of the 404 page - to tell the user something has gone wrong and provide them with a link back to your actual content.</strong></p><p>That's it! You can check out the full <a href="https://unsplash.com/developers">Unsplash API</a> for more advanced ideas. What ideas can you come up with? This is a simple one but maybe it can put a smile on someone's face. At the very least, when something's gone wrong on this site, it'll put one on mine.</p>]]></content:encoded></item><item><title><![CDATA[Sympathy for the Developer]]></title><description><![CDATA[How being a developer can make you a better designer.]]></description><link>https://dermothughes.com/sympathy-for-the-developer/</link><guid isPermaLink="false">Ghost__Post__5e861eb7c8d6ae0dd8177665</guid><category><![CDATA[Blog]]></category><pubDate>Thu, 02 Apr 2020 17:26:46 GMT</pubDate><media:content url="https://blog.dermothughes.com/content/images/2020/04/codebg.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.dermothughes.com/content/images/2020/04/codebg.jpg" alt="Sympathy for the Developer"/><p>How being a developer can make you a better designer.</p><!--kg-card-begin: markdown--><blockquote>
<p>Pleased to meet you<br>
Hope you guess my name<br>
'Cause what's confusing you is just the<br>
Nature of my game<br>
-<em>The Rolling Stones &quot;Sympathy For The Devil&quot;</em></br></br></br></br></p>
</blockquote>
<!--kg-card-end: markdown--><p>In University, as I entered my second year I had a choice to make. What modules to choose? There seemed to be an obvious fork in the road. Choose the more "Designer" modules such as graphic design and 3D modelling, or take the more "Developer" road and take modules on programming.</p><p>My career trajectory ever since has been leading me down a developer path, but my degree was still routed in design and it's been a sometimes painful journey, especially when I compare myself with colleagues with backgrounds in Computer Science or Software Development. <strong><strong>Programming is hard.</strong> </strong>At least, it certainly is for me. I don't believe anyone who say's isn't. Lately it's becoming clear to me that design and development are not as mutually exclusive as i previously thought. It's not one or the other, at least if you want to be a good designer.</p><!--kg-card-begin: markdown--><blockquote>
<p>Learning to code can make you a better designer.</p>
</blockquote>
<!--kg-card-end: markdown--><p>It's all about breaking down large and difficult problems into small, simple, manageable tasks. Taking on any project can seem a daunting task, and you can get overwhelmed very easily - but programming teaches you how to break this seemingly insurmountable task into bite-sized components. When creating a wireframe, rather than thinking of the design purely as a whole, think how a developer would approach building it. Break the design into its individual components as the projects building blocks.</p><p>Having some knowledge on the actual technologies the product will be built on can help so much. You don't need to know exactly how to build it yourself, but having a decent knowledge of HTML, CSS, JavaScript, or whatever it's built with means you can think about how a developer would need to go about it. The developer decides <em><em>how</em></em> something is built, but the designer decides <em><em>what</em></em> is built. An architect needs to have knowledge of physics and engineering to build a bridge, the builder doesn't need to know this - they can just follow the plans. Developers don't tend to read up on best UX practices, they don't keep up to date on the latest design trends or blogs. They spend their time browsing documentation, reading StackOverflow posts, and watching videos explaining concepts. These tutorials are aimed at explaining concepts and that something "works". These guides never consider real life or the end user's experience. That's where you as a designer needs to take charge.</p><p>Knowing what a developer may encounter can present many eventualities you'd never have thought of purely from a design perspective. <a href="https://github.com/kdeldycke/awesome-falsehood">Awesome FalseHood </a>is a fantastic curated list of assumptions programmers make including dates, emails, names, addresses, and geography.</p><!--kg-card-begin: markdown--><blockquote>
<p>Falsehoods programmers believe about names</p>
<ul>
<li>People's names do not change</li>
<li>People’s names have an order to them</li>
<li>My system will never have to deal with names from China</li>
<li>I can safely assume that this dictionary of bad words contains no people’s names in it</li>
<li>People have names</li>
</ul>
</blockquote>
<!--kg-card-end: markdown--><p>Thinking about your design from a developers perspective will not only help your design from the get-go, but once the design eventually lands on your developers desk it will streamline their job, and maybe you can be just a little less confused about the nature of their game.</p>]]></content:encoded></item></channel></rss>