Collective #722





Collective 722 item image

DOM ready events considered harmful

Jake and Cassie talk about DOM ready events, which can slow down your app in unexpected ways. But what are the alternatives? Learn more about it in this episode of HTTP 203.

Watch it


Collective 722 item image

The Study of Shaders with React Three Fiber

A complete guide on how to use shaders with React Three Fiber, work with uniforms and varyings, and build dynamic, interactive and composable materials with them through 8 unique 3D scenes. By Maxime Heckel.

Read it


Collective 722 item image

WebGi Camera Landing Page

A template for buildind scrollable landing pages with GSAP, ScrollTrigger and WebGi engine in TypeScript using the Parcel bundler. By Anderson Mancini.

Check it out










Collective 722 item image

Can SVG Symbols affect web performance?

When it comes to repeatable SVGs, most people would create a component and reuse it anywhere they want. There’s a high chance, that you would be missing some performance freebies. If you’re striving for that bang-for-the-buck kind of performance, this article might be interesting for you.

Check it out


Collective 722 item image

UpToDate what?

UpToDate tracks versions of popular projects so that you can check easily what the latest stable release is.

Check it out





Collective 722 item image

A good reset

A response to Jeremy’s post on “Control”, and why developers opt for divs, not buttons. By Trys Mudford.

Read it



Collective 722 item image

UI Buttons

Collection of hand-picked free HTML and CSS button code examples. Updated as of July 2022. 100 new items.

Check it out


Collective 722 item image

Maintenance Matters

Annie Kiley shares a list of ten simple things the team at Viget does to make their projects as maintainable as possible, regardless of the stack.

Read it



Collective #721






Collective 721 item image

PowerGlitch

PowerGlitch is a standalone library with no external dependencies. It leverages CSS animations to create a glitch effect on images. No canvas or DOM manipulations are needed.

Check it out


Collective 721 item image

Merge

Merge is an intelligent service that orchestrates merging pull requests to maintain a repository of code that always passes tests.

Check it out



Collective 721 item image

Endpoint Space

An easy solution to get notifications from forms on your website. Setup in 2 minutes – unlimited use.

Check it out





Collective 721 item image

YouCode

YouCode is an ad-free, private, specialized search engine for the developer community to seamlessly search time-saving apps like GitHub, StackOverflow, ArXiv, Mozilla Developer Network, and more.

Check it out



Collective 721 item image

Upgraderoo

Upgraderoo helps you easily upgrade your NPM packages so you can focus on building the features.

Check it out



Collective 721 item image

Card Leader

WebGL experiment featuring different lerp speeds for each card on mousemove. By Michal Zalobny.

Check it out




Collective 721 item image

Git Story

Tell the story of your Git project by creating MP4 video animations of your commit history directly from your Git repo.

Check it out


The post Collective #721 appeared first on Codrops.

Collective #717




State of GraphQL

The new annual developer survey of the GraphQL ecosystem. Fill out the survey to help identify which GraphQL tools and features are actually being used.

Check it out










ATMOS

Get on board and discover the most surreal facts about the aviation industry. A fantastic experiment by the folks of Leeroy.

Check it out




Markwhen

In case you didn’t know about it: Markwhen is a text-to-timeline tool. You write markdown-ish text and it gets converted into a nice looking cascading timeline.

Check it out



Modern CSS Reset

CSS Reset that uses modern CSS features such as :where(), logical properties, prefers-reduced-motion and more. By Elly.

Check it out





The post Collective #717 appeared first on Codrops.

Collective #716







Fresh

Just in time edge rendering, island based interactivity, and no configuration TypeScript support using Deno.

Check it out


Untools

A collection of thinking tools and frameworks to help you solve problems, make decisions and understand systems.

Check it out


Software Engineering

Addy Osmani shares some of the software engineering soft skills he has learned from his first 10 years on Google Chrome.

Read it















Calendar.txt

Keep your calendar in a plain text file with Calendar.txt. It is versionable, supports all operating systems and easily syncs with Android mobile phone.

Check it out


Patterns

The W3C Web Accessibility Initiative (WAI) shares some essential patterns for understanding when and why to use ARIA.

Check it out



Sake

Sake is a command runner for local and remote hosts. You define servers and tasks in a sake.yaml config file and then run the tasks on the servers.

Check it out

The post Collective #716 appeared first on Codrops.

Building Interoperable Web Components That Even Work With React

Those of us who’ve been web developers more than a few years have probably written code using more than one JavaScript framework. With all the choices out there — React, Svelte, Vue, Angular, Solid — it’s all but inevitable. One of the more frustrating things we have to deal with when working across frameworks is re-creating all those low-level UI components: buttons, tabs, dropdowns, etc. What’s particularly frustrating is that we’ll typically have them defined in one framework, say React, but then need to rewrite them if we want to build something in Svelte. Or Vue. Or Solid. And so on.

Wouldn’t it be better if we could define these low-level UI components once, in a framework-agnostic way, and then re-use them between frameworks? Of course it would! And we can; web components are the way. This post will show you how.

As of now, the SSR story for web components is a bit lacking. Declarative shadow DOM (DSD) is how a web component is server-side rendered, but, as of this writing, it’s not integrated with your favorite application frameworks like Next, Remix or SvelteKit. If that’s a requirement for you, be sure to check the latest status of DSD. But otherwise, if SSR isn’t something you’re using, read on.

First, some context

Web Components are essentially HTML elements that you define yourself, like <yummy-pizza> or whatever, from the ground up. They’re covered all over here at CSS-Tricks (including an extensive series by Caleb Williams and one by John Rhea) but we’ll briefly walk through the process. Essentially, you define a JavaScript class, inherit it from HTMLElement, and then define whatever properties, attributes and styles the web component has and, of course, the markup it will ultimately render to your users.

Being able to define custom HTML elements that aren’t bound to any particular component is exciting. But this freedom is also a limitation. Existing independently of any JavaScript framework means you can’t really interact with those JavaScript frameworks. Think of a React component which fetches some data and then renders some other React component, passing along the data. This wouldn’t really work as a web component, since a web component doesn’t know how to render a React component.

Web components particularly excel as leaf components. Leaf components are the last thing to be rendered in a component tree. These are the components which receive some props, and render some UI. These are not the components sitting in the middle of your component tree, passing data along, setting context, etc. — just pure pieces of UI that will look the same, no matter which JavaScript framework is powering the rest of the app.

The web component we’re building

Rather than build something boring (and common), like a button, let’s build something a little bit different. In my last post we looked at using blurry image previews to prevent content reflow, and provide a decent UI for users while our images load. We looked at base64 encoding a blurry, degraded versions of our images, and showing that in our UI while the real image loaded. We also looked at generating incredibly compact, blurry previews using a tool called Blurhash.

That post showed you how to generate those previews and use them in a React project. This post will show you how to use those previews from a web component so they can be used by any JavaScript framework.

But we need to walk before we can run, so we’ll walk through something trivial and silly first to see exactly how web components work.

Everything in this post will build vanilla web components without any tooling. That means the code will have a bit of boilerplate, but should be relatively easy to follow. Tools like Lit or Stencil are designed for building web components and can be used to remove much of this boilerplate. I urge you to check them out! But for this post, I’ll prefer a little more boilerplate in exchange for not having to introduce and teach another dependency.

A simple counter component

Let’s build the classic “Hello World” of JavaScript components: a counter. We’ll render a value, and a button that increments that value. Simple and boring, but it’ll let us look at the simplest possible web component.

In order to build a web component, the first step is to make a JavaScript class, which inherits from HTMLElement:

class Counter extends HTMLElement {}

The last step is to register the web component, but only if we haven’t registered it already:

if (!customElements.get("counter-wc")) {
  customElements.define("counter-wc", Counter);
}

And, of course, render it:

<counter-wc></counter-wc>

And everything in between is us making the web component do whatever we want it to. One common lifecycle method is connectedCallback, which fires when our web component is added to the DOM. We could use that method to render whatever content we’d like. Remember, this is a JS class inheriting from HTMLElement, which means our this value is the web component element itself, with all the normal DOM manipulation methods you already know and love.

At it’s most simple, we could do this:

class Counter extends HTMLElement {
  connectedCallback() {
    this.innerHTML = "<div style='color: green'>Hey</div>";
  }
}

if (!customElements.get("counter-wc")) {
  customElements.define("counter-wc", Counter);
}

…which will work just fine.

The word "hey" in green.

Adding real content

Let’s add some useful, interactive content. We need a <span> to hold the current number value and a <button> to increment the counter. For now, we’ll create this content in our constructor and append it when the web component is actually in the DOM:

constructor() {
  super();
  const container = document.createElement('div');

  this.valSpan = document.createElement('span');

  const increment = document.createElement('button');
  increment.innerText = 'Increment';
  increment.addEventListener('click', () => {
    this.#value = this.#currentValue + 1;
  });

  container.appendChild(this.valSpan);
  container.appendChild(document.createElement('br'));
  container.appendChild(increment);

  this.container = container;
}

connectedCallback() {
  this.appendChild(this.container);
  this.update();
}

If you’re really grossed out by the manual DOM creation, remember you can set innerHTML, or even create a template element once as a static property of your web component class, clone it, and insert the contents for new web component instances. There’s probably some other options I’m not thinking of, or you can always use a web component framework like Lit or Stencil. But for this post, we’ll continue to keep it simple.

Moving on, we need a settable JavaScript class property named value

#currentValue = 0;

set #value(val) {
  this.#currentValue = val;
  this.update();
}

It’s just a standard class property with a setter, along with a second property to hold the value. One fun twist is that I’m using the private JavaScript class property syntax for these values. That means nobody outside our web component can ever touch these values. This is standard JavaScript that’s supported in all modern browsers, so don’t be afraid to use it.

Or feel free to call it _value if you prefer. And, lastly, our update method:

update() {
  this.valSpan.innerText = this.#currentValue;
}

It works!

The counter web component.

Obviously this is not code you’d want to maintain at scale. Here’s a full working example if you’d like a closer look. As I’ve said, tools like Lit and Stencil are designed to make this simpler.

Adding some more functionality

This post is not a deep dive into web components. We won’t cover all the APIs and lifecycles; we won’t even cover shadow roots or slots. There’s endless content on those topics. My goal here is to provide a decent enough introduction to spark some interest, along with some useful guidance on actually using web components with the popular JavaScript frameworks you already know and love.

To that end, let’s enhance our counter web component a bit. Let’s have it accept a color attribute, to control the color of the value that’s displayed. And let’s also have it accept an increment property, so consumers of this web component can have it increment by 2, 3, 4 at a time. And to drive these state changes, let’s use our new counter in a Svelte sandbox — we’ll get to React in a bit.

We’ll start with the same web component as before and add a color attribute. To configure our web component to accept and respond to an attribute, we add a static observedAttributes property that returns the attributes that our web component listens for.

static observedAttributes = ["color"];

With that in place, we can add a attributeChangedCallback lifecycle method, which will run whenever any of the attributes listed in observedAttributes are set, or updated.

attributeChangedCallback(name, oldValue, newValue) {
  if (name === "color") {
    this.update();
  }
}

Now we update our update method to actually use it:

update() {
  this.valSpan.innerText = this._currentValue;
  this.valSpan.style.color = this.getAttribute("color") || "black";
}

Lastly, let’s add our increment property:

increment = 1;

Simple and humble.

Using the counter component in Svelte

Let’s use what we just made. We’ll go into our Svelte app component and add something like this:

<script>
  let color = "red";
</script>

<style>
  main {
    text-align: center;
  }
</style>

<main>
  <select bind:value={color}>
    <option value="red">Red</option>
    <option value="green">Green</option>
    <option value="blue">Blue</option>
  </select>

  <counter-wc color={color}></counter-wc>
</main>

And it works! Our counter renders, increments, and the dropdown updates the color. As you can see, we render the color attribute in our Svelte template and, when the value changes, Svelte handles the legwork of calling setAttribute on our underlying web component instance. There’s nothing special here: this is the same thing it already does for the attributes of any HTML element.

Things get a little bit interesting with the increment prop. This is not an attribute on our web component; it’s a prop on the web component’s class. That means it needs to be set on the web component’s instance. Bear with me, as things will wind up much simpler in a bit.

First, we’ll add some variables to our Svelte component:

let increment = 1;
let wcInstance;

Our powerhouse of a counter component will let you increment by 1, or by 2:

<button on:click={() => increment = 1}>Increment 1</button>
<button on:click={() => increment = 2}>Increment 2</button>

But, in theory, we need to get the actual instance of our web component. This is the same thing we always do anytime we add a ref with React. With Svelte, it’s a simple bind:this directive:

<counter-wc bind:this={wcInstance} color={color}></counter-wc>

Now, in our Svelte template, we listen for changes to our component’s increment variable and set the underlying web component property.

$: {
  if (wcInstance) {
    wcInstance.increment = increment;
  }
}

You can test it out over at this live demo.

We obviously don’t want to do this for every web component or prop we need to manage. Wouldn’t it be nice if we could just set increment right on our web component, in markup, like we normally do for component props, and have it, you know, just work? In other words, it’d be nice if we could delete all usages of wcInstance and use this simpler code instead:

<counter-wc increment={increment} color={color}></counter-wc>

It turns out we can. This code works; Svelte handles all that legwork for us. Check it out in this demo. This is standard behavior for pretty much all JavaScript frameworks.

So why did I show you the manual way of setting the web component’s prop? Two reasons: it’s useful to understand how these things work and, a moment ago, I said this works for “pretty much” all JavaScript frameworks. But there’s one framework which, maddeningly, does not support web component prop setting like we just saw.

React is a different beast

React. The most popular JavaScript framework on the planet does not support basic interop with web components. This is a well-known problem that’s unique to React. Interestingly, this is actually fixed in React’s experimental branch, but for some reason wasn’t merged into version 18. That said, we can still track the progress of it. And you can try this yourself with a live demo.

The solution, of course, is to use a ref, grab the web component instance, and manually set increment when that value changes. It looks like this:

import React, { useState, useRef, useEffect } from 'react';
import './counter-wc';

export default function App() {
  const [increment, setIncrement] = useState(1);
  const [color, setColor] = useState('red');
  const wcRef = useRef(null);

  useEffect(() => {
    wcRef.current.increment = increment;
  }, [increment]);

  return (
    <div>
      <div className="increment-container">
        <button onClick={() => setIncrement(1)}>Increment by 1</button>
        <button onClick={() => setIncrement(2)}>Increment by 2</button>
      </div>

      <select value={color} onChange={(e) => setColor(e.target.value)}>
        <option value="red">Red</option>
        <option value="green">Green</option>
        <option value="blue">Blue</option>
      </select>

      <counter-wc ref={wcRef} increment={increment} color={color}></counter-wc>
    </div>
  );
}

As we discussed, coding this up manually for every web component property is simply not scalable. But all is not lost because we have a couple of options.

Option 1: Use attributes everywhere

We have attributes. If you clicked the React demo above, the increment prop wasn’t working, but the color correctly changed. Can’t we code everything with attributes? Sadly, no. Attribute values can only be strings. That’s good enough here, and we’d be able to get somewhat far with this approach. Numbers like increment can be converted to and from strings. We could even JSON stringify/parse objects. But eventually we’ll need to pass a function into a web component, and at that point we’d be out of options.

Option 2: Wrap it

There’s an old saying that you can solve any problem in computer science by adding a level of indirection (except the problem of too many levels of indirection). The code to set these props is pretty predictable and simple. What if we hide it in a library? The smart folks behind Lit have one solution. This library creates a new React component for you after you give it a web component, and list out the properties it needs. While clever, I’m not a fan of this approach.

Rather than have a one-to-one mapping of web components to manually-created React components, what I prefer is just one React component that we pass our web component tag name to (counter-wc in our case) — along with all the attributes and properties — and for this component to render our web component, add the ref, then figure out what is a prop and what is an attribute. That’s the ideal solution in my opinion. I don’t know of a library that does this, but it should be straightforward to create. Let’s give it a shot!

This is the usage we’re looking for:

<WcWrapper wcTag="counter-wc" increment={increment} color={color} />

wcTag is the web component tag name; the rest are the properties and attributes we want passed along.

Here’s what my implementation looks like:

import React, { createElement, useRef, useLayoutEffect, memo } from 'react';

const _WcWrapper = (props) => {
  const { wcTag, children, ...restProps } = props;
  const wcRef = useRef(null);

  useLayoutEffect(() => {
    const wc = wcRef.current;

    for (const [key, value] of Object.entries(restProps)) {
      if (key in wc) {
        if (wc[key] !== value) {
          wc[key] = value;
        }
      } else {
        if (wc.getAttribute(key) !== value) {
          wc.setAttribute(key, value);
        }
      }
    }
  });

  return createElement(wcTag, { ref: wcRef });
};

export const WcWrapper = memo(_WcWrapper);

The most interesting line is at the end:

return createElement(wcTag, { ref: wcRef });

This is how we create an element in React with a dynamic name. In fact, this is what React normally transpiles JSX into. All our divs are converted to createElement("div") calls. We don’t normally need to call this API directly but it’s there when we need it.

Beyond that, we want to run a layout effect and loop through every prop that we’ve passed to our component. We loop through all of them and check to see if it’s a property with an in check that checks the web component instance object as well as its prototype chain, which will catch any getters/setters that wind up on the class prototype. If no such property exists, it’s assumed to be an attribute. In either case, we only set it if the value has actually changed.

If you’re wondering why we use useLayoutEffect instead of useEffect, it’s because we want to immediately run these updates before our content is rendered. Also, note that we have no dependency array to our useLayoutEffect; this means we want to run this update on every render. This can be risky since React tends to re-render a lot. I ameliorate this by wrapping the whole thing in React.memo. This is essentially the modern version of React.PureComponent, which means the component will only re-render if any of its actual props have changed — and it checks whether that’s happened via a simple equality check.

The only risk here is that if you’re passing an object prop that you’re mutating directly without re-assigning, then you won’t see the updates. But this is highly discouraged, especially in the React community, so I wouldn’t worry about it.

Before moving on, I’d like to call out one last thing. You might not be happy with how the usage looks. Again, this component is used like this:

<WcWrapper wcTag="counter-wc" increment={increment} color={color} />

Specifically, you might not like passing the web component tag name to the <WcWrapper> component and prefer instead the @lit-labs/react package above, which creates a new individual React component for each web component. That’s totally fair and I’d encourage you to use whatever you’re most comfortable with. But for me, one advantage with this approach is that it’s easy to delete. If by some miracle React merges proper web component handling from their experimental branch into main tomorrow, you’d be able to change the above code from this:

<WcWrapper wcTag="counter-wc" increment={increment} color={color} />

…to this:

<counter-wc ref={wcRef} increment={increment} color={color} />

You could probably even write a single codemod to do that everywhere, and then delete <WcWrapper> altogether. Actually, scratch that: a global search and replace with a RegEx would probably work.

The implementation

I know, it seems like it took a journey to get here. If you recall, our original goal was to take the image preview code we looked at in my last post, and move it to a web component so it can be used in any JavaScript framework. React’s lack of proper interop added a lot of detail to the mix. But now that we have a decent handle on how to create a web component, and use it, the implementation will almost be anti-climactic.

I’ll drop the entire web component here and call out some of the interesting bits. If you’d like to see it in action, here’s a working demo. It’ll switch between my three favorite books on my three favorite programming languages. The URL for each book will be unique each time, so you can see the preview, though you’ll likely want to throttle things in your DevTools Network tab to really see things taking place.

View entire code
class BookCover extends HTMLElement {
  static observedAttributes = ['url'];

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'url') {
      this.createMainImage(newValue);
    }
  }

  set preview(val) {
    this.previewEl = this.createPreview(val);
    this.render();
  }

  createPreview(val) {
    if (typeof val === 'string') {
      return base64Preview(val);
    } else {
      return blurHashPreview(val);
    }
  }

  createMainImage(url) {
    this.loaded = false;
    const img = document.createElement('img');
    img.alt = 'Book cover';
    img.addEventListener('load', () =&gt; {
      if (img === this.imageEl) {
        this.loaded = true;
        this.render();
      }
    });
    img.src = url;
    this.imageEl = img;
  }

  connectedCallback() {
    this.render();
  }

  render() {
    const elementMaybe = this.loaded ? this.imageEl : this.previewEl;
    syncSingleChild(this, elementMaybe);
  }
}

First, we register the attribute we’re interested in and react when it changes:

static observedAttributes = ['url'];

attributeChangedCallback(name, oldValue, newValue) {
  if (name === 'url') {
    this.createMainImage(newValue);
  }
}

This causes our image component to be created, which will show only when loaded:

createMainImage(url) {
  this.loaded = false;
  const img = document.createElement('img');
  img.alt = 'Book cover';
  img.addEventListener('load', () => {
    if (img === this.imageEl) {
      this.loaded = true;
      this.render();
    }
  });
  img.src = url;
  this.imageEl = img;
}

Next we have our preview property, which can either be our base64 preview string, or our blurhash packet:

set preview(val) {
  this.previewEl = this.createPreview(val);
  this.render();
}

createPreview(val) {
  if (typeof val === 'string') {
    return base64Preview(val);
  } else {
    return blurHashPreview(val);
  }
}

This defers to whichever helper function we need:

function base64Preview(val) {
  const img = document.createElement('img');
  img.src = val;
  return img;
}

function blurHashPreview(preview) {
  const canvasEl = document.createElement('canvas');
  const { w: width, h: height } = preview;

  canvasEl.width = width;
  canvasEl.height = height;

  const pixels = decode(preview.blurhash, width, height);
  const ctx = canvasEl.getContext('2d');
  const imageData = ctx.createImageData(width, height);
  imageData.data.set(pixels);
  ctx.putImageData(imageData, 0, 0);

  return canvasEl;
}

And, lastly, our render method:

connectedCallback() {
  this.render();
}

render() {
  const elementMaybe = this.loaded ? this.imageEl : this.previewEl;
  syncSingleChild(this, elementMaybe);
}

And a few helpers methods to tie everything together:

export function syncSingleChild(container, child) {
  const currentChild = container.firstElementChild;
  if (currentChild !== child) {
    clearContainer(container);
    if (child) {
      container.appendChild(child);
    }
  }
}

export function clearContainer(el) {
  let child;

  while ((child = el.firstElementChild)) {
    el.removeChild(child);
  }
}

It’s a little bit more boilerplate than we’d need if we build this in a framework, but the upside is that we can re-use this in any framework we’d like — although React will need a wrapper for now, as we discussed.

Odds and ends

I’ve already mentioned Lit’s React wrapper. But if you find yourself using Stencil, it actually supports a separate output pipeline just for React. And the good folks at Microsoft have also created something similar to Lit’s wrapper, attached to the Fast web component library.

As I mentioned, all frameworks not named React will handle setting web component properties for you. Just note that some have some special flavors of syntax. For example, with Solid.js, <your-wc value={12}> always assumes that value is a property, which you can override with an attr prefix, like <your-wc attr:value={12}>.

Wrapping up

Web components are an interesting, often underused part of the web development landscape. They can help reduce your dependence on any single JavaScript framework by managing your UI, or “leaf” components. While creating these as web components — as opposed to Svelte or React components — won’t be as ergonomic, the upside is that they’ll be widely reusable.


Building Interoperable Web Components That Even Work With React originally published on CSS-Tricks. You should get the newsletter.

Dialog Components: Go Native HTML or Roll Your Own?

As the author of a library called AgnosticUI, I’m always on the lookout for new components. And recently, I decided to dig in and start work on a new dialog (aka modal) component. That’s something many devs like to have in their toolset and my goal was to make the best one possible, with an extra special focus on making it inclusive and accessible.

My first thought was that I would avoid any dependencies and bite the bullet to build my own dialog component. As you may know, there’s a new <dialog> element making the rounds and I figured using it as a starting point would be the right thing, especially in the inclusiveness and accessibilities departments.

But, after doing some research, I instead elected to leverage a11y-dialog by Kitty Giraudel. I even wrote adapters so it integrates smoothly with Vue 3, Svelte, and Angular. Kitty has long offered a React adapter as well.

Why did I go that route? Let me take you through my thought process.

First question: Should I even use the native <dialog> element?

The native <dialog> element is being actively improved and will likely be the way forward. But, it still has some issues at the moment that Kitty pointed out quite well:

  1. Clicking the backdrop overlay does not close the dialog by default
  2. The alertdialog ARIA role used for alerts simply does not work with the native <dialog> element. We’re supposed to use that role when a dialog requires a user’s response and shouldn’t be closed by clicking the backdrop, or by pressing ESC.
  3. The <dialog> element comes with a ::backdrop pseudo-element but it is only available when a dialog is programmatically opened with dialog.showModal().

And as Kitty also points out, there are general issues with the element’s default styles, like the fact they are left to the browser and will require JavaScript. So, it’s sort of not 100% HTML anyway.

Here’s a pen demonstrating these points:

Now, some of these issues may not affect you or whatever project you’re working on specifically, and you may even be able to work around things. If you still would like to utilize the native dialog you should see Adam Argyle’s wonderful post on building a dialog component with native dialog.

OK, let’s discuss what actually are the requirements for an accessible dialog component…

What I’m looking for

I know there are lots of ideas about what a dialog component should or should not do. But as far as what I was personally going after for AgnosticUI hinged on what I believe make for an accessible dialog experience:

  1. The dialog should close when clicking outside the dialog (on the backdrop) or when pressing the ESC key.
  2. It should trap focus to prevent tabbing out of the component with a keyboard.
  3. It should allow forwarding tabbing with TAB and backward tabbing with SHIFT+TAB.
  4. It should return focus back to the previously focused element when closed.
  5. It should correctly apply aria-* attributes and toggles.
  6. It should provide Portals (only if we’re using it within a JavaScript framework).
  7. It should support the alertdialog ARIA role for alert situations.
  8. It should prevent the underlying body from scrolling, if needed.
  9. It would be great if our implementation could avoid the common pitfalls that come with the native <dialog> element.
  10. It would ideally provide a way to apply custom styling while also taking the prefers-reduced-motion user preference query as a further accessibility measure.

I’m not the only one with a wish list. You might want to see Scott O’Hara’s article on the topic as well as Kitty’s full write-up on creating an accessible dialog from scratch for more in-depth coverage.

It should be clear right about now why I nixed the native <dialog> element from my component library. I believe in the work going into it, of course, but my current needs simply outweigh the costs of it. That’s why I went with Kitty’s a11y-dialog as my starting point.

Auditing <dialog> accessibility

Before trusting any particular dialog implementation, it’s worth making sure it fits the bill as far as your requirements go. With my requirements so heavily leaning on accessibility, that meant auditing a11y-dialog.

Accessibility audits are a profession of their own. And even if it’s not my everyday primary focus, I know there are some things that are worth doing, like:

This is quite a lot of work, as you might imagine (or know from experience). It’s tempting to take a path of less resistance and try automating things but, in a study conducted by Deque Systems, automated tooling can only catch about 57% of accessibility issues. There’s no substitute for good ol’ fashioned hard work.

The auditing environment

The dialog component can be tested in lots of places, including Storybook, CodePen, CodeSandbox, or whatever. For this particular test, though, I prefer instead to make a skeleton page and test locally. This way I’m preventing myself from having to validate the validators, so to speak. Having to use, say, a Storybook-specific add-on for a11y verification is fine if you’re already using Storybook on your own components, but it adds another layer of complexity when testing the accessibility of an external component.

A skeleton page can verify the dialog with manual checks, existing a11y tooling, and screen readers. If you’re following along, you’ll want to run this page via a local server. There are many ways to do that; one is to use a tool called serve, and npm even provides a nice one-liner npx serve <DIRECTORY> command to fire things up.

Let’s do an example audit together!

I’m obviously bullish on a11y-dialog here, so let’s put it to the test and verify it using some of the the recommended approaches we’ve covered.

Again, all I’m doing here is starting with an HTML. You can use the same one I am (complete with styles and scripts baked right in).

View full code
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>A11y Dialog Test</title>
    <style>
      .dialog-container {
        display: flex;
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        right: 0;
        z-index: 2;
      }
      
      .dialog-container[aria-hidden='true'] {
        display: none;
      }
      
      .dialog-overlay {
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        right: 0;
        background-color: rgb(43 46 56 / 0.9);
        animation: fade-in 200ms both;
      }
      
      .dialog-content {
        background-color: rgb(255, 255, 255);
        margin: auto;
        z-index: 2;
        position: relative;
        animation: fade-in 400ms 200ms both, slide-up 400ms 200ms both;
        padding: 1em;
        max-width: 90%;
        width: 600px;
        border-radius: 2px;
      }
      
      @media screen and (min-width: 700px) {
        .dialog-content {
          padding: 2em;
        }
      }
      
      @keyframes fade-in {
        from {
          opacity: 0;
        }
      }
      
      @keyframes slide-up {
        from {
          transform: translateY(10%);
        }
      }

      /* Note, for brevity we haven't implemented prefers-reduced-motion */
      
      .dialog h1 {
        margin: 0;
        font-size: 1.25em;
      }
      
      .dialog-close {
        position: absolute;
        top: 0.5em;
        right: 0.5em;
        border: 0;
        padding: 0;
        background-color: transparent;
        font-weight: bold;
        font-size: 1.25em;
        width: 1.2em;
        height: 1.2em;
        text-align: center;
        cursor: pointer;
        transition: 0.15s;
      }
      
      @media screen and (min-width: 700px) {
        .dialog-close {
          top: 1em;
          right: 1em;
        }
      }
      
      * {
        box-sizing: border-box;
      }
      
      body {
        font: 125% / 1.5 -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif;
        padding: 2em 0;
      }
      
      h1 {
        font-size: 1.6em;
        line-height: 1.1;
        font-family: 'ESPI Slab', sans-serif;
        margin-bottom: 0;
      }
      
      main {
        max-width: 700px;
        margin: 0 auto;
        padding: 0 1em;
      }
    </style>
    <script defer src="https://cdn.jsdelivr.net/npm/a11y-dialog@7/dist/a11y-dialog.min.js"></script>
  </head>

  <body>
    <main>
      <div class="dialog-container" id="my-dialog" aria-hidden="true" aria-labelledby="my-dialog-title" role="dialog">
        <div class="dialog-overlay" data-a11y-dialog-hide></div>
        <div class="dialog-content" role="document">
          <button data-a11y-dialog-hide class="dialog-close" aria-label="Close this dialog window">
            ×
          </button>
          <a href="https://www.yahoo.com/" target="_blank">Rando Yahoo Link</a>
  
          <h1 id="my-dialog-title">My Title</h1>
          <p id="my-dialog-description">
            Some description of what's inside this dialog…
          </p>
        </div>
      </div>
      <button type="button" data-a11y-dialog-show="my-dialog">
        Open the dialog
      </button>
    </main>
    <script>
      // We need to ensure our deferred A11yDialog has
      // had a chance to do its thing ;-)
      window.addEventListener('DOMContentLoaded', (event) => {
        const dialogEl = document.getElementById('my-dialog')
        const dialog = new A11yDialog(dialogEl)
      });
    </script>
  </body>

</html>

I know, we’re ignoring a bunch of best practices (what, styles in the <head>?!) and combined all of the HTML, CSS, and JavaScript in one file. I won’t go into the details of the code as the focus here is testing for accessibility, but know that this test requires an internet connection as we are importing a11y-dialog from a CDN.

First, the manual checks

I served this one-pager locally and here are my manual check results:

FeatureResult
It should close when clicking outside the dialog (on the backdrop) or when pressing the ESC key.
It ought to trap focus to prevent tabbing out of the component with a keyboard.
It should allow forwarding tabbing with TAB and backward tabbing with SHIFT+TAB.
It should return focus back to the previously focused element when closed.
It should correctly apply aria-* attributes and toggles.
I verified this one “by eye” after inspecting the elements in the DevTools Elements panel.
It should provide Portals.Not applicable.
This is only useful when implementing the element with React, Svelte, Vue, etc. We’ve statically placed it on the page with aria-hidden for this test.
It should support for the alertdialog ARIA role for alert situations.
You’ll need to do two things:

First, remove data-a11y-dialog-hide from the overlay in the HTML so that it is <div class="dialog-overlay"></div>. Replace the dialog role with alertdialog so that it becomes:

<div class="dialog-container" id="my-dialog" aria-hidden="true" aria-labelledby="my-dialog-title" aria-describedby="my-dialog-description" role="alertdialog">

Now, clicking on the overlay outside of the dialog box does not close the dialog, as expected.
It should prevent the underlying body from scrolling, if needed.
I didn’t manually test but this, but it is clearly available per the documentation.
It should avoid the common pitfalls that come with the native <dialog> element.
This component does not rely on the native <dialog> which means we’re good here.

Next, let’s use some a11y tooling

I used Lighthouse to test the component both on a desktop computer and a mobile device, in two different scenarios where the dialog is open by default, and closed by default.

a11y-dialog Lighthouse testing, score 100.

I’ve found that sometimes the tooling doesn’t account for DOM elements that are dynamically shown or hidden DOM elements, so this test ensures I’m getting full coverage of both scenarios.

I also tested with IBM Equal Access Accessibility Checker. Generally, this tool will give you a red violation error if there’s anything egregious wrong. It will also ask you to manually review certain items. As seen here, there a couple of items for manual review, but no red violations.

a11y-dialog — tested with IBM Equal Access Accessibility Checker

Moving on to screen readers

Between my manual and tooling checks, I’m already feeling relatively confident that a11y-dialog is an accessible option for my dialog of choice. However, we ought to do our due diligence and consult a screen reader.

VoiceOver is the most convenient screen reader for me since I work on a Mac at the moment, but JAWS and NVDA are big names to look at as well. Like checking for UI consistency across browsers, it’s probably a good idea to test on more than one screen reader if you can.

VoiceOver caption over the a11y-modal example.

Here’s how I conducted the screen reader part of the audit with VoiceOver. Basically, I mapped out what actions needed testing and confirmed each one, like a script:

StepResult
The dialog component’s trigger button is announced.“Entering A11y Dialog Test, web content.”
The dialog should open when pressing CTRL+ALT +Space should show the dialog.“Dialog. Some description of what’s inside this dialog. You are currently on a dialog, inside of web content.”
The dialog should TAB to and put focus on the component’s Close button.“Close this dialog button. You are currently on a button, inside of web content.”
Tab to the link element and confirm it is announced.“Link, Rando Yahoo Link”
Pressing the SPACE key while focused on the Close button should close the dialog component and return to the last item in focus.

Testing with people

If you’re thinking we’re about to move on to testing with real people, I was unfortunately unable to find someone. If I had done this, though, I would have used a similar set of steps for them to run through while I observe, take notes, and ask a few questions about the general experience.

As you can see, a satisfactory audit involves a good deal of time and thought.

Fine, but I want to use a framework’s dialog component

That’s cool! Many frameworks have their own dialog component solution, so there’s lots to choose from. I don’t have some amazing spreadsheet audit of all the frameworks and libraries in the wild, and will spare you the work of evaluating them all.

Instead, here are some resources that might be good starting points and considerations for using a dialog component in some of the most widely used frameworks.

Disclaimer: I have not tested these personally. This is all stuff I found while researching.

Angular dialog options

In 2020, Deque published an article that audits Angular component libraries and the TL;DR was that Material (and its Angular/CDK library) and ngx-bootstrap both appear to provide decent dialog accessibility.

React dialog options

Reakit offers a dialog component that they claim is compliant with WAI-ARIA dialog guidelines, and chakra-ui appears to pay attention to its accessibility. Of course, Material is also available for React, so that’s worth a look as well. I’ve also heard good things about reach/dialog and Adobe’s @react-aria/dialog.

Vue dialog options

I’m a fan of Vuetensils, which is Austin Gil’s naked (aka headless) components library, which just so happens to have a dialog component. There’s also Vuetify, which is a popular Material implementation with a dialog of its own. I’ve also crossed paths with PrimeVue, but was surprised that its dialog component failed to return focus to the original element.

Svelte dialog options

You might want to look at svelte-headlessui. Material has a port in svelterial that is also worth a look. It seems that many current SvelteKit users prefer to build their own component sets as SvelteKit’s packaging idiom makes it super simple to do. If this is you, I would definitely recommend considering svelte-a11y-dialog as a convenient means to build custom dialogs, drawers, bottom sheets, etc.

I’ll also point out that my AgnosticUI library wraps the React, Vue, Svelte and Angular a11y-dialog adapter implementations we’ve been talking about earlier.

Bootstrap, of course

Bootstrap is still something many folks reach for, and unsurprisingly, it offers a dialog component. It requires you to follow some steps in order to make the modal accessible.

If you have other inclusive and accessible library-based dialog components that merit consideration, I’d love to know about them in the comments!

But I’m creating a custom design system

If you’re creating a design system or considering some other roll-your-own dialog approach, you can see just how many things need to be tested and taken into consideration… all for one component! It’s certainly doable to roll your own, of course, but I’d say it’s also extremely prone to error. You might ask yourself whether the effort is worthwhile when there are already battle-tested options to choose from.

I’ll simply leave you with something Scott O’Hara — co-editor of ARIA in HTML and HTML AAM specifications in addition to just being super helpful with all things accessibility — points out:

You could put in the effort to add in those extensions, or you could use a robust plugin like a11y-dialog and ensure that your dialogs will have a pretty consistent experience across all browsers.

Back to my objective…

I need that dialog to support React, Vue, Svelte, and Angular implementations.

I mentioned earlier that a11y-dialog already has ports for Vue and React. But the Vue port hasn’t yet been updated for Vue 3. Well, I was quite happy to spend the time I would have spent creating what likely would have been a buggy hand-rolled dialog component toward helping update the Vue port. I also added a Svelte port and one for Angular too. These are both very new and I would consider them experimental beta software at time of writing. Feedback welcome, of course!

It can support other components, too!

I think it’s worth pointing out that a dialog uses the same underlying concept for hiding and showing that can be used for a drawer (aka off-canvas) component. For example, if we borrow the CSS we used in our dialog accessibility audit and add a few additional classes, then a11y-dialog can be transformed into a working and effective drawer component:

.drawer-start { right: initial; }
.drawer-end { left: initial; }
.drawer-top { bottom: initial; }
.drawer-bottom { top: initial; }

.drawer-content {
  margin: initial;
  max-width: initial;
  width: 25rem;
  border-radius: initial;
}

.drawer-top .drawer-content,
.drawer-bottom .drawer-content {
  width: 100%;
}

These classes are used in an additive manner, essentially extending the base dialog component. This is exactly what I have started to do as I add my own drawer component to AgnosticUI. Saving time and reusing code FTW!

Wrapping up

Hopefully I’ve given you a good idea of the thinking process that goes into the making and maintenance of a component library. Could I have hand-rolled my own dialog component for the library? Absolutely! But I doubt it would have yielded better results than what a resource like Kitty’s a11y-dialog does, and the effort is daunting. There’s something cool about coming up with your own solution — and there may be good situations where you want to do that — but probably not at the cost of sacrificing something like accessibility.

Anyway, that’s how I arrived at my decision. I learned a lot about the native HTML <dialog> and its accessibility along the way, and I hope my journey gave you some of those nuggets too.


Dialog Components: Go Native HTML or Roll Your Own? originally published on CSS-Tricks. You should get the newsletter.

Syntax Highlighting (and More!) With Prism on a Static Site

So, you’ve decided to build a blog with Next.js. Like any dev blogger, you’d like to have code snippets in your posts that are formatted nicely with syntax highlighting. Perhaps you also want to display line numbers in the snippets, and maybe even have the ability to call out certain lines of code.

This post will show you how to get that set up, as well as some tips and tricks for getting these other features working. Some of it is tricker than you might expect.

Prerequisites

We’re using the Next.js blog starter as the base for our project, but the same principles should apply to other frameworks. That repo has clear (and simple) getting started instructions. Scaffold the blog, and let’s go!

Another thing we’re using here is Prism.js, a popular syntax highlighting library that’s even used right here on CSS-Tricks. The Next.js blog starter uses Remark to convert Markdown into markup, so we’ll use the remark-Prism.js plugin for formatting our code snippets.

Basic Prism.js integration

Let’s start by integrating Prism.js into our Next.js starter. Since we already know we’re using the remark-prism plugin, the first thing to do is install it with your favorite package manager:

npm i remark-prism

Now go into the markdownToHtml file, in the /lib folder, and switch on remark-prism:

import remarkPrism from "remark-prism";

// later ...

.use(remarkPrism, { plugins: ["line-numbers"] })

Depending on which version of the remark-html you’re using, you might also need to change its usage to .use(html, { sanitize: false }).

The whole module should now look like this:

import { remark } from "remark";
import html from "remark-html";
import remarkPrism from "remark-prism";

export default async function markdownToHtml(markdown) {
  const result = await remark()
    .use(html, { sanitize: false })
    .use(remarkPrism, { plugins: ["line-numbers"] })
    .process(markdown);

  return result.toString();
}

Adding Prism.js styles and theme

Now let’s import the CSS that Prism.js needs to style the code snippets. In the pages/_app.js file, import the main Prism.js stylesheet, and the stylesheet for whichever theme you’d like to use. I’m using Prism.js’s “Tomorrow Night” theme, so my imports look like this:

import "prismjs/themes/prism-tomorrow.css";
import "prismjs/plugins/line-numbers/prism-line-numbers.css";
import "../styles/prism-overrides.css";

Notice I’ve also started a prism-overrides.css stylesheet where we can tweak some defaults. This will become useful later. For now, it can remain empty.

And with that, we now have some basic styles. The following code in Markdown:

```js
class Shape {
  draw() {
    console.log("Uhhh maybe override me");
  }
}

class Circle {
  draw() {
    console.log("I'm a circle! :D");
  }
}
```

…should format nicely:

Adding line numbers

You might have noticed that the code snippet we generated does not display line numbers even though we imported the plugin that supports it when we imported remark-prism. The solution is hidden in plain sight in the remark-prism README:

Don’t forget to include the appropriate css in your stylesheets.

In other words, we need to force a .line-numbers CSS class onto the generated <pre> tag, which we can do like this:

And with that, we now have line numbers!

Note that, based on the version of Prism.js I have and the “Tomorrow Night” theme I chose, I needed to add this to the prism-overrides.css file we started above:

.line-numbers span.line-numbers-rows {
  margin-top: -1px;
}

You may not need that, but there you have it. We have line numbers!

Highlighting lines

Our next feature will be a bit more work. This is where we want the ability to highlight, or call out certain lines of code in the snippet.

There’s a Prism.js line-highlight plugin; unfortunately, it is not integrated with remark-prism. The plugin works by analyzing the formatted code’s position in the DOM, and manually highlights lines based on that information. That’s impossible with the remark-prism plugin since there is no DOM at the time the plugin runs. This is, after all, static site generation. Next.js is running our Markdown through a build step and generating HTML to render the blog. All of this Prism.js code runs during this static site generation, when there is no DOM.

But fear not! There’s a fun workaround that fits right in with CSS-Tricks: we can use plain CSS (and a dash of JavaScript) to highlight lines of code.

Let me be clear that this is a non-trivial amount of work. If you don’t need line highlighting, then feel free to skip to the next section. But if nothing else, it can be a fun demonstration of what’s possible.

Our base CSS

Let’s start by adding the following CSS to our prism-overrides.css stylesheet:

:root {
  --highlight-background: rgb(0 0 0 / 0);
  --highlight-width: 0;
}

.line-numbers span.line-numbers-rows > span {
  position: relative;
}

.line-numbers span.line-numbers-rows > span::after {
  content: " ";
  background: var(--highlight-background);
  width: var(--highlight-width);
  position: absolute;
  top: 0;
}

We’re defining some CSS custom properties up front: a background color and a highlight width. We’re setting them to empty values for now. Later, though, we’ll set meaningful values in JavaScript for the lines we want highlighted.

We’re then setting the line number <span> to position: relative, so that we can add a ::after pseudo element with absolute positioning. It’s this pseudo element that we’ll use to highlight our lines.

Declaring the highlighted lines

Now, let’s manually add a data attribute to the <pre> tag that’s generated, then read that in code, and use JavaScript to tweak the styles above to highlight specific lines of code. We can do this the same way that we added line numbers before:

This will cause our <pre> element to be rendered with a data-line="3,8-10" attribute, where line 3 and lines 8-10 are highlighted in the code snippet. We can comma-separate line numbers, or provide ranges.

Let’s look at how we can parse that in JavaScript, and get highlighting working.

Reading the highlighted lines

Head over to components/post-body.tsx. If this file is JavaScript for you, feel free to either convert it to TypeScript (.tsx), or just ignore all my typings.

First, we’ll need some imports:

import { useEffect, useRef } from "react";

And we need to add a ref to this component:

const rootRef = useRef<HTMLDivElement>(null);

Then, we apply it to the root element:

<div ref={rootRef} className="max-w-2xl mx-auto">

The next piece of code is a little long, but it’s not doing anything crazy. I’ll show it, then walk through it.

useEffect(() => {
  const allPres = rootRef.current.querySelectorAll("pre");
  const cleanup: (() => void)[] = [];

  for (const pre of allPres) {
    const code = pre.firstElementChild;
    if (!code || !/code/i.test(code.tagName)) {
      continue;
    }

    const highlightRanges = pre.dataset.line;
    const lineNumbersContainer = pre.querySelector(".line-numbers-rows");

    if (!highlightRanges || !lineNumbersContainer) {
      continue;
    }

    const runHighlight = () =>
      highlightCode(pre, highlightRanges, lineNumbersContainer);
    runHighlight();

    const ro = new ResizeObserver(runHighlight);
    ro.observe(pre);

    cleanup.push(() => ro.disconnect());
  }

  return () => cleanup.forEach(f => f());
}, []);

We’re running an effect once, when the content has all been rendered to the screen. We’re using querySelectorAll to grab all the <pre> elements under this root element; in other words, whatever blog post the user is viewing.

For each one, we make sure there’s a <code> element under it, and we check for both the line numbers container and the data-line attribute. That’s what dataset.line checks. See the docs for more info.

If we make it past the second continue, then highlightRanges is the set of highlights we declared earlier which, in our case, is "3,8-10", where lineNumbersContainer is the container with the .line-numbers-rows CSS class.

Lastly, we declare a runHighlight function that calls a highlightCode function that I’m about to show you. Then, we set up a ResizeObserver to run that same function anytime our blog post changes size, i.e., if the user resizes the browser window.

The highlightCode function

Finally, let’s see our highlightCode function:

function highlightCode(pre, highlightRanges, lineNumberRowsContainer) {
  const ranges = highlightRanges.split(",").filter(val => val);
  const preWidth = pre.scrollWidth;

  for (const range of ranges) {
    let [start, end] = range.split("-");
    if (!start || !end) {
      start = range;
      end = range;
    }

    for (let i = +start; i <= +end; i++) {
      const lineNumberSpan: HTMLSpanElement = lineNumberRowsContainer.querySelector(
        `span:nth-child(${i})`
      );
      lineNumberSpan.style.setProperty(
        "--highlight-background",
        "rgb(100 100 100 / 0.5)"
      );
      lineNumberSpan.style.setProperty("--highlight-width", `${preWidth}px`);
    }
  }
}

We get each range and read the width of the <pre> element. Then we loop through each range, find the relevant line number <span>, and set the CSS custom property values for them. We set whatever highlight color we want, and we set the width to the total scrollWidth value of the <pre> element. I kept it simple and used "rgb(100 100 100 / 0.5)" but feel free to use whatever color you think looks best for your blog.

Here’s what it looks like:

Syntax highlighting for a block of Markdown code.

Line highlighting without line numbers

You may have noticed that all of this so far depends on line numbers being present. But what if we want to highlight lines, but without line numbers?

One way to implement this would be to keep everything the same and add a new option to simply hide those line numbers with CSS. First, we’ll add a new CSS class, .hide-numbers:

```js[class="line-numbers"][class="hide-numbers"][data-line="3,8-10"]
class Shape {
  draw() {
    console.log("Uhhh maybe override me");
  }
}

class Circle {
  draw() {
    console.log("I'm a circle! :D");
  }
}
```

Now let’s add CSS rules to hide the line numbers when the .hide-numbers class is applied:

.line-numbers.hide-numbers {
  padding: 1em !important;
}
.hide-numbers .line-numbers-rows {
  width: 0;
}
.hide-numbers .line-numbers-rows > span::before {
  content: " ";
}
.hide-numbers .line-numbers-rows > span {
  padding-left: 2.8em;
}

The first rule undoes the shift to the right from our base code in order to make room for the line numbers. By default, the padding of the Prism.js theme I chose is 1em. The line-numbers plugin increases it to 3.8em, then inserts the line numbers with absolute positioning. What we did reverts the padding back to the 1em default.

The second rule takes the container of line numbers, and squishes it to have no width. The third rule erases all of the line numbers themselves (they’re generated with ::before pseudo elements).

The last rule simply shifts the now-empty line number <span> elements back to where they would have been so that the highlighting can be positioned how we want it. Again, for my theme, the line numbers normally adds 3.8em worth of left padding, which we reverted back to the default 1em. These new styles add the other 2.8em so things are back to where they should be, but with the line numbers hidden. If you’re using different plugins, you might need slightly different values.

Here’s what the result looks like:

Syntax highlighting for a block of Markdown code.

Copy-to-Clipboard feature

Before we wrap up, let’s add one finishing touch: a button allowing our dear reader to copy the code from our snippet. It’s a nice little enhancement that spares people from having to manually select and copy the code snippets.

It’s actually somewhat straightforward. There’s a navigator.clipboard.writeText API for this. We pass that method the text we’d like to copy, and that’s that. We can inject a button next to every one of our <code> elements to send the code’s text to that API call to copy it. We’re already messing with those <code> elements in order to highlight lines, so let’s integrate our copy-to-clipboard button in the same place.

First, from the useEffect code above, let’s add one line:

useEffect(() => {
  const allPres = rootRef.current.querySelectorAll("pre");
  const cleanup: (() => void)[] = [];

  for (const pre of allPres) {
    const code = pre.firstElementChild;
    if (!code || !/code/i.test(code.tagName)) {
      continue;
    }

    pre.appendChild(createCopyButton(code));

Note the last line. We’re going to append our button right into the DOM underneath our <pre> element, which is already position: relative, allowing us to position the button more easily.

Let’s see what the createCopyButton function looks like:

function createCopyButton(codeEl) {
  const button = document.createElement("button");
  button.classList.add("prism-copy-button");
  button.textContent = "Copy";

  button.addEventListener("click", () => {
    if (button.textContent === "Copied") {
      return;
    }
    navigator.clipboard.writeText(codeEl.textContent || "");
    button.textContent = "Copied";
    button.disabled = true;
    setTimeout(() => {
      button.textContent = "Copy";
      button.disabled = false;
    }, 3000);
  });

  return button;
}

Lots of code, but it’s mostly boilerplate. We create our button then give it a CSS class and some text. And then, of course, we create a click handler to do the copying. After the copy is done, we change the button’s text and disable it for a few seconds to help give the user feedback that it worked.

The real work is on this line:

navigator.clipboard.writeText(codeEl.textContent || "");

We’re passing codeEl.textContent rather than innerHTML since we want only the actual text that’s rendered, rather than all the markup Prism.js adds in order to format our code nicely.

Now let’s see how we might style this button. I’m no designer, but this is what I came up with:

.prism-copy-button {
  position: absolute;
  top: 5px;
  right: 5px;
  width: 10ch;
  background-color: rgb(100 100 100 / 0.5);
  border-width: 0;
  color: rgb(0, 0, 0);
  cursor: pointer;
}

.prism-copy-button[disabled] {
  cursor: default;
}

Which looks like this:

Syntax highlighting for a block of Markdown code.

And it works! It copies our code, and even preserves the formatting (i.e. new lines and indentation)!

Wrapping up

I hope this has been useful to you. Prism.js is a wonderful library, but it wasn’t originally written for static sites. This post walked you through some tips and tricks for bridging that gap, and getting it to work well with a Next.js site.


Syntax Highlighting (and More!) With Prism on a Static Site originally published on CSS-Tricks. You should get the newsletter.

Writing Strong Front-end Test Element Locators

Automated front-end tests are awesome. We can write a test with code to visit a page — or load up just a single component — and have that test code click on things or type text like a user would, then make assertions about the state of the application after the interactions. This lets us confirm that everything described in the tests work as expected in the application.

Since this post is about one of the building blocks of any automated UI tests, I don’t assume too much prior knowledge. Feel free to skip the first couple of sections if you’re already familiar with the basics.

Structure of a front-end test

There’s a classic pattern that’s useful to know when writing tests: Arrange, Act, Assert. In front-end tests, this translates to a test file that does the following:

  1. Arrange: Get things ready for the test. Visit a certain page, or mount a certain component with the right props, mock some state, whatever.
  2. Act: Do something to the application. Click a button, fill out a form, etc. Or not, for simple state-checks, we can skip this.
  3. Assert: Check some stuff. Did submitting a form show a thank you message? Did it send the right data to the back end with a POST?

In specifying what to interact with and then later what to check on the page, we can use various element locators to target the parts of the DOM we need to use.

A locator can be something like an element’s ID, the text content of an element, or a CSS selector, like .blog-post or even article > div.container > div > div > p:nth-child(12). Anything about an element that can identify that element to your test runner can be a locator. As you can probably already tell from that last CSS selector, locators come in many varieties.

We often evaluate locators in terms of being brittle or stable. In general, we want the most stable element locators possible so that our test can always find the element it needs, even if the code around the element is changing over time. That said, maximizing stability at all costs can lead to defensive test-writing that actually weakens the tests. We get the most value by having a combination of brittleness and stability that aligns with what we want our tests to care about.

In this way, element locators are like duct tape. They should be really strong in one direction, and tear easily in the other direction. Our tests should hold together and keep passing when unimportant changes are made to the application, but they should readily fail when important changes happen that contradict what we’ve specified in the test.

Beginner’s guide to element locators in front-end testing

First, let’s pretend we are writing instructions for an actual person to do their job. A new gate inspector has just been hired at Gate Inspectors, Inc. You are their boss, and after everybody’s been introduced you are supposed to give them instructions for inspecting their first gate. If you want them to be successful, you probably would not write them a note like this:

Go past the yellow house, keep going ‘til you hit the field where Mike’s mother’s friend’s goat went missing that time, then turn left and tell me if the gate in front of the house across the street from you opens or not.

Those directions are kind of like using a long CSS selector or XPath as a locator. It’s brittle — and it’s the “bad kind of brittle”. If the yellow house gets repainted and you repeat the steps, you can’t find the gate anymore, and might decide to give up (or in this case, the test fails).

Likewise, if you don’t know about Mike’s mother’s friend’s goat situation, you can’t stop at the right reference point to know what gate to check. This is exactly what makes the “bad kind of brittle” bad — the test can break for all kinds of reasons, and none of those reasons have anything to do with the usability of the gate.

So let’s make a different front-end test, one that’s much more stable. After all, legally in this area, all gates on a given road are supposed to have unique serial numbers from the maker:

Go to the gate with serial number 1234 and check if it opens.

This is more like locating an element by its ID. It’s more stable and it’s only one step. All the points of failure from the last test have been removed. This test will only fail if the gate with that ID doesn’t open as expected.

Now, as it turns out, though no two gates should have the same ID on the same road, that’s not actually enforced anywhere And one day, another gate on the road ends up with the same ID.

So the next time the newly hired gate inspector goes to test “Gate 1234,” they find that other one first, and are now visiting the wrong house and checking the wrong thing. The test might fail, or worse: if that gate works as expected, the test still passes but it’s not testing the intended subject. It provides false confidence. It would keep passing even if our original target gate was stolen in the middle of the night, by gate thieves.

After an incident like this, it’s clear that IDs are not as stable as we thought. So, we do some next-level thinking and decide that, on the inside of the gate, we’d like a special ID just for testing. We’ll send out a tech to put the special ID on just this one gate. The new test instructions look like this:

Go to the gate with Test ID “my-favorite-gate” and check if it opens.

This one is like using the popular data-testid attribute. Attributes like this are great because it is obvious in the code that they are intended for use by automated tests and shouldn’t be changed or removed. As long as the gate has that attribute, you will always find the gate. Just like IDs, uniqueness is still not enforced, but it’s a bit more likely.

This is about as far away from brittle as you can get, and it confirms the functionality of the gate. We don’t depend on anything except the attribute we deliberately added for testing. But there’s a bit of problem hiding here…

This is a user interface test for the gate, but the locator is something that no user would ever use to find the gate.

It’s a missed opportunity because, in this imaginary county, it turns out gates are required to have the house number printed on them so that people can see the address. So, all gates should have an unique human-facing label, and if they don’t, it’s a problem in and of itself.

When locating the gate with the test ID, if it turns out that the gate has been repainted and the house number covered up, our test would still pass. But the whole point of the gate is for people to access the house. In other words, a working gate that a user can’t find should still be a test failure, and we want a locator that is capable of revealing this problem.

Here’s another pass at this test instruction for the gate inspector on their first day:

Go to the gate for house number 40 and check if it opens.

This one uses a locator that adds value to the test: it depends on something users also depend on, which is the label for the gate. It adds back a potential reason for the test to fail before it reaches the interaction we want to actually test, which might seem bad at first glance. But in this case, because the locator is meaningful from a user’s perspective, we shouldn’t shrug this off as “brittle.” If the gate can’t be found by its label, it doesn’t matter if it opens or not — this is is the “good kind of brittle”.

The DOM matters

A lot of front-end testing advice tells us to avoid writing locators that depend on DOM structure. This means that developers can refactor components and pages over time and let the tests confirm that user-facing workflows haven’t broken, without having to update tests to catch up to the new structure. This principle is useful, but I would tweak it a bit to say we ought to avoid writing locators that depend on irrelevant DOM structure in our front-end testing.

For an application to function correctly, the DOM should reflect the nature and structure of the content that’s on the screen at any given time. One reason for this is accessibility. A DOM that’s correct in this sense is much easier for assistive technology to parse properly and describe to users who aren’t seeing the contents rendered by the browser. DOM structure and plain old HTML make a huge difference to the independence of users who rely on assistive technology.

Let’s spin up a front-end test to submit something to the contact form of our app. We’ll use Cypress for this, but the principles of choosing locators strategically apply to all front-end testing frameworks that use the DOM for locating elements. Here we find elements, enter some test, submit the form, and verify the “thank you” state is reached:

// 👎 Not recommended
cy.get('#name').type('Mark')
cy.get('#comment').type('test comment')
cy.get('.submit-btn').click()
cy.get('.thank-you').should('be.visible')

There are all kinds of implicit assertions happening in these four lines. cy.get() is checking that the element exists in the DOM. The test will fail if the element doesn’t exist after a certain time, while actions like type and click verify that the elements are visible, enabled, and unobstructed by something else before taking an action.

So, we get a lot “for free” even in a simple test like this, but we’ve also introduced some dependencies upon things we (and our users) don’t really care about. The specific ID and classes that we are checking seem stable enough, especially compared to selectors like div.main > p:nth-child(3) > span.is-a-button or whatever. Those long selectors are so specific that a minor change to the DOM could cause a test to fail because it can’t find the element, not because the functionality is broken.

But even our short selectors, like #name, come with three problems:

  1. The ID could be changed or removed in the code, causing the element to go overlooked, especially if the form might appear more than once on a page. A unique ID might need to be generated for each instance, and that’s not something we can easily pre-fill into a test.
  2. If there is more than one instance of a form on the page and they have the same ID, we need to decide which form to fill out.
  3. We don’t actually care what the ID is from a user perspective, so all the built-in assertions are kind of… not fully leveraged?

For problems one and two, the recommended solution is often to use dedicated data attributes in our HTML that are added exclusively for testing. This is better because our tests no longer depend on the DOM structure, and as a developer modifies the code around a component, the tests will continue to pass without needing an update, as long as they keep the data-test="name-field" attached to the right input element.

This doesn’t address problem three though — we still have a front-end interaction test that depends on something that is meaningless to the user.

Meaningful locators for interactive elements

Element locators are meaningful when they depend on something we actually want to depend on because something about the locator is important to the user experience. In the case of interactive elements, I would argue that the best selector to use is the element’s accessible name. Something like this is ideal:

// 👍 Recommended
cy.getByLabelText('Name').type('Mark')

This example uses the byLabelText helper from Cypress Testing Library. (In fact, if you are using Testing Library in any form, it is probably already helping you write accessible locators like this.)

This is useful because now the built-in checks (that we get for free through the cy.type() command) include the accessibility of the form field. All interactive elements should have an accessible name that is exposed to assistive technology. This is how, for example, a screenreader user would know what the form field they are typing into is called in order to enter the needed information.

The way to provide this accessible name for a form field is usually through a label element associated with the field by an ID. The getByLabelText command from Cypress Testing Library verifies that the field is labeled appropriately, but also that the field itself is an element that’s allowed to have a label. So, for example, the following HTML would correctly fail before the type() command is attempted, because even though a label is present, labeling a div is invalid HTML:

<!-- 👎 Not recommended  -->
<label for="my-custom-input">Editable DIV element:</label>
<div id="my-custom-input" contenteditable="true" />

Because this is invalid HTML, screenreader software could never associate this label with this field correctly. To fix this, we would update the markup to use a real input element:

<!-- 👍 Recommended -->
<label for="my-real-input">Real input:</label>
<input id="my-real-input" type="text" />

This is awesome. Now if the test fails at this point after edits made to the DOM, it’s not because of an irrelevant structure changes to how elements are arranged, but because our edits did something to break a part of DOM that our front-end tests explicitly care about, that would actually matter to users.

Meaningful locators for non-interactive elements

For non-interactive elements, we should put on our thinking caps. Let’s use a little bit of judgement before falling back on the data-cy or data-test attributes that will always be there for us if the DOM doesn’t matter at all.

Before we dip into the generic locators, let’s remember: the state of the DOM is our Whole Thing™ as web developers (at least, I think it is). And the DOM drives the UX for everybody who is not experiencing the page visually. So a lot of the time, there might be some meaningful element that we could or should be using in the code that we can include in a test locator.

And if there’s not, because. say, the application code is all generic containers like div and span, we should consider fixing up the application code first, while adding the test. Otherwise there is a risk of having our tests actually specify that the generic containers are expected and desired, making it a little harder for somebody to modify that component to be more accessible.

This topic opens up a can of worms about how accessibility works in an organization. Often, if nobody is talking about it and it’s not a part of the practice at our companies, we don’t take accessibility seriously as front-end developers. But at the end of the day, we are supposed to be the experts in what is the “right markup” for design, and what to consider in deciding that. I discuss this side of things a lot more in my talk from Connect.Tech 2021, called “Researching and Writing Accessible Vue… Thingies”.

As we saw above, with the elements we traditionally think of as interactive, there is a pretty good rule of thumb that’s easy to build into our front-end tests: interactive elements should have perceivable labels correctly associated to the element. So anything interactive, when we test it, should be selected from the DOM using that required label.

For elements that we don’t think of as interactive — like most content-rendering elements that display pieces of text of whatever, aside from some basic landmarks like main — we wouldn’t trigger any Lighthouse audit failures if we put the bulk of our non-interactive content into generic div or span containers. But the markup won’t be very informative or helpful to assistive technology because it’s not describing the nature and structure of the content to somebody who can’t see it. (To see this taken to an extreme, check out Manuel Matuzovic’s excellent blog post, “Building the most inaccessible site possible with a perfect Lighthouse score.”)

The HTML we render is where we communicate important contextual information to anybody who is not perceiving the content visually. The HTML is used to build the DOM, the DOM is used to create the browser’s accessibility tree, and the accessibility tree is the API that assistive technologies of all kinds can use to express the content and the actions that can be taken to a disabled person using our software. A screenreader is often the first example we think of, but the accessibility tree can also be used by other technology, like displays that turn webpages into Braille, among others.

Automated accessibility checks won’t tell us if we’ve really created the right HTML for the content. The “rightness” of the HTML a judgement call we are making developers about what information we think needs to be communicated in the accessibility tree.

Once we’ve made that call, we can decide how much of that is suitable to bake into the automated front-end testing.

Let’s say that we have decided that a container with the status ARIA role will hold the “thank you” and error messaging for a contact form. This might be nice so that the feedback for the form’s success or failure can be announced by a screenreader. CSS classes of .thank-you and .error could be applied to control the visual state.

If we add this element and want to write a UI test for it, we might write an assertion like this after the test fills out the form and submits it:

// 👎 Not recommended
cy.get('.thank-you').should('be.visible')

Or even a test that uses a non-brittle but still meaningless selector like this:

// 👎 Not recommended
cy.get('[data-testid="thank-you-message"]').should('be.visible')

Both could be rewritten using cy.contains():

// 👍 Recommended
cy.contains('[role="status"]', 'Thank you, we have received your message')
  .should('be.visible')

This would confirm that the expected text appeared and was inside the right kind of container. Compared to the previous test, this has much more value in terms of verifying actual functionality. If any part of this test fails, we’d want to know, because both the message and the element selector are important to us and shouldn’t be changed trivially.

We have definitely gained some coverage here without a lot of extra code, but we’ve also introduced a different kind of brittleness. We have plain English strings in our tests, and that means if the “thank you” message changes to something like “Thank you for reaching out!” this test suddenly fails. Same with all the other tests. A small change to how a label is written would require updating any test that targets elements using that label.

We can improve this by using the same source of truth for these strings in front-end tests as we do in our code. And if we currently have human-readable sentences embedded right there in the HTML of our components… well now we have a reason to pull that stuff out of there.

Human-readable strings might be the magic numbers of UI code

A magic number (or less-excitingly, an “unnamed numerical constant”) is that super-specific value you sometimes see in code that is important to the end result of a calculation, like a good old 1.023033 or something. But since that number is not unlabeled, its significance is unclear, and so it’s unclear what it’s doing. Maybe it applies a tax. Maybe it compensates for some bug that we don’t know about. Who knows?

Either way, the same is true for front-end testing and the general advice is to avoid magic numbers because of their lack of clarity. Code reviews will often catch them and ask what the number is for. Can we move it into a constant? We also do the same thing if a value is to be reused multiple places. Rather than repeat the value everywhere, we can store it in a variable and use the variable as needed.

Writing user interfaces over the years, I’ve come to see text content in HTML or template files as very similar to magic numbers in other contexts. We’re putting absolute values all through our code, but in reality it might be more useful to store those values elsewhere and bring them in at build time (or even through an API depending on the situation).

There are a few reasons for this:

  1. I used to work with clients who wanted to drive everything from a content management system. Content, even form labels and status messages, that didn’t live in the CMS were to be avoided. Clients wanted full control so that content changes didn’t require editing code and re-deploying the site. That makes sense; code and content are different concepts.
  2. I’ve worked in many multilingual codebases where all the text needs to be pulled in through some internationalization framework, like this:
<label for="name">
  <!-- prints "Name" in English but something else in a different language -->
  {{content[currentLanguage].contactForm.name}}
</label>
  1. As far as front-end testing goes, our UI tests are much more robust if, instead of checking for a specific “thank you” message we hardcode into the test, we do something like this:
const text = content.en.contactFrom // we would do this once and all tests in the file can read from it

cy.contains(text.nameLabel, '[role="status"]').should('be.visible')

Every situation is different, but having some system of constants for strings is a huge asset when writing robust UI tests, and I would recommend it. Then, if and when translation or dynamic content does become necessary for our situation, we are a lot better prepared for it.

I’ve heard good arguments against importing text strings in tests, too. For example, some find tests are more readable and generally better if the test itself specifies the expected content independently from the content source.

It’s a fair point. I’m less persuaded by this because I think content should be controlled through more of an editorial review/publishing model, and I want the test to check if the expected content from the source got rendered, not some specific strings that were correct when the test was written. But plenty of people disagree with me on this, and I say as long as within a team the tradeoff is understood, either choice is acceptable.

That said, it’s still a good idea to isolate code from content in the front end more generally. And sometimes it might even be valid to mix and match — like importing strings in our component tests and not importing them in our end-to-end tests. This way, we save some duplication and gain confidence that our components display correct content, while still having front-end tests that independently assert the expected text, in the editorial, user-facing sense.

When to use data-test locators

CSS selectors like [data-test="success-message"] are still useful and can be very helpful when used in an intentional way, instead of used all the time. If our judgement is that there’s no meaningful, accessible way to target an element, data-test attributes are still the best option. They are much better than, say, depending on a coincidence like whatever the DOM structure happens to be on the day you are writing the test, and falling back to the “second list item in the third div with a class of card” style of test.

There are also times when content is expected to be dynamic and there’s no way to easily grab strings from some common source of truth to use in our tests. In those situations, a data-test attribute helps us reach the specific element we care about. It can still be combined with an accessibility-friendly assertion, for example:

cy.get('h2[data-test="intro-subheading"]')

Here we want to find what has the data-test attribute of intro-subheading, but still allow our test to assert that it should be a h2 element if that’s what we expect it to be. The data-test attribute is used to make sure we get the specific h2 we are interested in, not some other h2 that might be on the page, if for some reason the content of that h2 can’t be known at the time of the test.

Even in cases where we do know the content, we might still use data attributes to make sure the application renders that content in the right spot:

cy.contains('h2[data-test="intro-subheading"]', 'Welcome to Testing!')

data-test selectors can also be a convenience to get down to a certain part of the page and then make assertions within that. This could look like the following:

cy.get('article[data-test="ablum-card-blur-great-escape"]').within(() => {
  cy.contains('h2', 'The Great Escape').should('be.visible')
  cy.contains('p', '1995 Album by Blur').should('be.visible')
  cy.get('[data-test="stars"]').should('have.length', 5)
})

At that point we get into some nuance because there may very well be other good ways to target this content, it’s just an example. But at the end of the day, it’s a good if worrying about details like that is where we are because at least we have some understanding of the accessibility features embedded in the HTML we are testing, and that we want to include those in our tests.

When the DOM matters, test it

Front-end tests bring a lot more value to us if we are thoughtful about how we tell the tests what elements to interact with, and what to contents to expect. We should prefer accessible names to target interactive components, and we should include specific elements names, ARIA roles, etc., for non-interactive content — if those things are relevant to the functionality. These locators, when practical, create the right combination of strength and brittleness.

And of course, for everything else, there’s data-test.


Writing Strong Front-end Test Element Locators originally published on CSS-Tricks. You should get the newsletter.

What Web Frameworks Solve And How To Do Without Them (Part 1)

I have recently become very interested in comparing frameworks to vanilla JavaScript. It started after some frustration I had using React in some of my freelance projects, and with my recent, more intimate acquaintance with web standards as a specification editor.

I was interested to see what are the commonalities and differences between the frameworks, what the web platform has to offer as a leaner alternative, and whether it’s sufficient. My objective is not to bash frameworks, but rather to understand the costs and benefits, to determine whether an alternative exists, and to see whether we can learn from it, even if we do decide to use a framework.

In this first part, I will deep dive into a few technical features common across frameworks, and how some of the different frameworks implement them. I will also look at the cost of using those frameworks.

The Frameworks

I chose four frameworks to look at: React, being the dominant one today, and three newer contenders that claim to do things differently from React.

  • React
    “React makes it painless to create interactive UIs. Declarative views make your code more predictable and easier to debug.”
  • SolidJS
    “Solid follows the same philosophy as React… It however has a completely different implementation that forgoes using a virtual DOM.”
  • Svelte
    “Svelte is a radical new approach to building user interfaces… a compile step that happens when you build your app. Instead of using techniques like virtual DOM diffing, Svelte writes code that surgically updates the DOM when the state of your app changes.”
  • Lit
    “Building on top of the Web Components standards, Lit adds just … reactivity, declarative templates, and a handful of thoughtful features.”

To summarize what the frameworks say about their differentiators:

  • React makes building UIs easier with declarative views.
  • SolidJS follows React’s philosophy but uses a different technique.
  • Svelte uses a compile-time approach to UIs.
  • Lit uses existing standards, with some added lightweight features.
What Frameworks Solve

The frameworks themselves mention the words declarative, reactivity, and virtual DOM. Let’s dive into what those mean.

Declarative Programming

Declarative programming is a paradigm in which logic is defined without specifying the control flow. We describe what the result needs to be, rather than what steps would take us there.

In the early days of declarative frameworks, circa 2010, DOM APIs were a lot more bare and verbose, and writing web applications with imperative JavaScript required a lot of boilerplate code. That’s when the concept of “model-view-viewmodel” (MVVM) became prevalent, with the then-groundbreaking Knockout and AngularJS frameworks, providing a JavaScript declarative layer that handled that complexity inside the library.

MVVM is not a widely used term today, and it’s somewhat of a variation of the older term “data-binding”.

Data Binding

Data binding is a declarative way to express how data is synchronized between a model and a user interface.

All of the popular UI frameworks provide some form of data-binding, and their tutorials start with a data-binding example.

Here is data-binding in JSX (SolidJS and React):

function HelloWorld() {
 const name = "Solid or React";

 return (
     <div>Hello {name}!</div>
 )
}

Data-binding in Lit:

class HelloWorld extends LitElement {
 @property()
 name = 'lit';

 render() {
   return html`<p>Hello ${this.name}!</p>`;
 }
}

Data-binding in Svelte:

<script>
  let name = 'world';
</script>

<h1>Hello {name}!</h1>

Reactivity

Reactivity is a declarative way to express the propagation of change.

When we have a way to declaratively express data-binding, we need an efficient way for the framework to propagate changes.

The React engine compares the result of rendering with the previous result, and it applies the difference to the DOM itself. This way of handling change propagation is called the virtual DOM.

In SolidJS, this is done more explicitly, with its store and built-in elements. For example, the Show element would keep track of what has changed internally, instead of the virtual DOM.

In Svelte, the “reactive” code is generated. Svelte knows which events can cause a change, and it generates straightforward code that draws the line between the event and the DOM change.

In Lit, reactivity is accomplished using element properties, essentially relying on the built-in reactivity of HTML custom elements.

Logic

When a framework provides a declarative interface for data-binding, with its implementation of reactivity, it also needs to provide some way to express some of the logic that is traditionally written imperatively. The basic building blocks of logic are “if” and “for”, and all of the major frameworks provide some expression of these building blocks.

Conditionals

Apart from binding basic data such as numbers and string, every framework supplies a “conditional” primitive. In React, it looks like this:

const [hasError, setHasError] = useState(false);  
return hasError ? <label>Message</label> : null;
…
setHasError(true);

SolidJS provides a built-in conditional component, Show:

<Show when={state.error}>
  <label>Message</label>
</Show>

Svelte provides the #if directive:

{#if state.error}
  <label>Message</label>
{/if}

In Lit, you’d use an explicit ternary operation in the render function:

render() {
 return this.error ? html`<label>Message</label>`: null;
}

Lists

The other common framework primitive is list-handling. Lists are a key part of UIs — list of contacts, notifications, etc. — and to work efficiently, they need to be reactive, not updating the whole list when one data item changes.

In React, list-handling looks like this:

contacts.map((contact, index) =>
 <li key={index}>
   {contact.name}
 </li>)

React uses the special key attribute to differentiate between list items, and it makes sure that the whole list doesn’t get replaced with every render.

In SolidJS, the for and index built-in elements are used:

<For each={state.contacts}>
  {contact => <DIV>{contact.name}</DIV> }
</For>

Internally, SolidJS uses its own store in conjunction with for and index to decide which elements to update when items change. It’s more explicit than React, allowing us to avoid the complexity of the virtual DOM.

Svelte uses the each directive, which gets transpiled based on its updaters:

{#each contacts as contact}
  <div>{contact.name}</div>
{/each}

Lit supplies a repeat function, which works similarly to React’s key-based list mapping:

repeat(contacts, contact => contact.id,
    (contact, index) => html`<div>${contact.name}</div>`

Component Model

One thing that is out of the scope of this article is the component model in the different frameworks and how it can be dealt with using custom HTML elements.

Note: This is a big subject, and I hope to cover it in a future article because this one would get too long. :)

The Cost

Frameworks provide declarative data-binding, control flow primitives (conditionals and lists), and a reactive mechanism to propagate changes.

They also provide other major things, such as a way to reuse components, but that’s a subject for a separate article.

Are frameworks useful? Yes. They give us all of these convenient features. But is that the right question to ask? Using a framework comes at a cost. Let’s see what those costs are.

Bundle Size

When looking at bundle size, I like looking at the minified non-Gzip’d size. That’s the size that is the most relevant to the CPU cost of JavaScript execution.

  • ReactDOM is about 120 KB.
  • SolidJS is about 18 KB.
  • Lit is about 16 KB.
  • Svelte is about 2 KB, but the size of the generated code varies.

It seems that today’s frameworks are doing a better job than React of keeping the bundle size small. The virtual DOM requires a lot of JavaScript.

Builds

Somehow we got used to “building” our web apps. It’s impossible to start a front-end project without setting up Node.js and a bundler such as Webpack, dealing with some recent configuration changes in the Babel-TypeScript starter pack, and all that jazz.

The more expressive and the smaller the bundle size of the framework, the bigger the burden of build tools and transpilation time.

Svelte claims that the virtual DOM is pure overhead. I agree, but perhaps “building” (as with Svelte and SolidJS) and custom client-side template engines (as with Lit) are also pure overhead, of a different kind?

Debugging

With building and transpilation come a different kind of cost.

The code we see when we use or debug the web app is totally different from what we wrote. We now rely on special debugging tools of varying quality to reverse engineer what happens on the website and to connect it with bugs in our own code.

In React, the call stack is never “yours” — React handles scheduling for you. This works great when there are no bugs. But try to identify the cause of infinite-loop re-renders and you’ll be in for a world of pain.

In Svelte, the bundle size of the library itself is small, but you’re going to ship and debug a whole bunch of cryptic generated code that is Svelte’s implementation of reactivity, customized to your app’s needs.

With Lit, it’s less about building, but to debug it effectively you have to understand its template engine. This might be the biggest reason why my sentiment towards frameworks is skeptical.

When you look for custom declarative solutions, you end up with more painful imperative debugging. The examples in this document use Typescript for API specification, but the code itself doesn’t require transpilation.

Upgrades

In this document, I’ve looked at four frameworks, but there are more frameworks than I can count (AngularJS, Ember.js, and Vue.js, to name a few). Can you count on the framework, its developers, its mindshare, and its ecosystem to work for you as it evolves?

One thing that is more frustrating than fixing your own bugs is having to find workarounds for framework bugs. And one thing that’s more frustrating than framework bugs are bugs that occur when you upgrade a framework to a new version without modifying your code.

True, this problem also exists in browsers, but when it occurs, it happens to everyone, and in most cases a fix or a published workaround is imminent. Also, most of the patterns in this document are based on mature web platform APIs; there is not always a need to go with the bleeding edge.

Summary

We dived a bit deeper into understanding the core problems frameworks try to solve and how they go about solving them, focusing on data-binding, reactivity, conditionals and lists. We also looked at the cost.

In Part 2 (coming up next week!), we will see how these problems can be addressed without using a framework at all, and what we can learn from it. Stay tuned!

Special thanks to the following individuals for technical reviews: Yehonatan Daniv, Tom Bigelajzen, Benjamin Greenbaum, Nick Ribal and Louis Lazaris.

What should someone learn about CSS if they last boned up during CSS3?

“CSS3” was a massive success for CSS. A whole bunch of stuff dropped essentially at once that was all very terrific to get our hands on in CSS. Gradients, animation/transition, border-radius, box-shadow, transformwoot! And better, the banner name CSS3 (and the spiritual umbrella “HTML5”) took off and the industry was just saturated in learning material about it all. Just look at all the “CSS3”-dubbed material that’s been published around here at CSS-Tricks over the years.

No doubt loads of people boned up on these technologies during that time. I also think there is no doubt there are lots of people that haven’t learned much CSS since then.

So what would we tell them?

Some other folks have speculated similarly. Scott Vandehey in “Modern CSS in a Nutshell” wrote about his friend who hasn’t kept up with CSS since about 2015 and doesn’t really know what to learn. I’ll attempt to paraphrase Scott’s list and what’s changed since the days of CSS3.

Preprocessors are still widely used since the day of CSS3, but the reasons to use them have dwindled, so maybe don’t even bother. This includes more newfangled approaches like polyfilling future features. This also includes Autoprefixer. CSS-in-JS is common, but only on projects where the entire workflow is already in JavaScript. You’ll know when you’re on a relevant project and can learn the syntax then if you need to. You should learn Custom Properties, Flexbox, and Grid for sure.

Sounds about right to me. But allow me to make my own list of post-CSS3 goodies that expands upon that list a smidge.

What’s new since CSS3?

And by “CSS3” let’s say 2015 or so.


.card {
  display: grid;
  grid-template-columns:
    150px 1fr;
  gap: 1rem;
}
.card .nav {
  display: flex;
  gap: 0.5rem;
}

Layout

You really gotta learn Flexbox and Grid if you haven’t — they are really cornerstones of CSS development these days. Even more so than any feature we got in CSS3.

Grid is extra powerful when you factor in subgrid and masonry, neither of which is reliable cross-browser yet but probably will be before too long.

html {
  --bgColor: #70f1d9;
  
  --font-size-base: 
    clamp(1.833rem, 2vw + 1rem, 3rem);
  --font-size-lrg:
    clamp(1.375rem, 2vw + 1rem, 2.25rem);
}

html.dark {
  --bgColor: #2d283e;
}

CSS Custom Properties

Custom properties are also a big deal for several reasons. They can be your home for design tokens on your project, making a project easier to maintain and keep consistent. Color theming is a big use case, like dark mode.

You can go so far as designing entire sites using mostly custom properties. And along those lines, you can’t ignore Tailwind these days. The approach of styling an entire site with classes in HTML strikes the right chord with a lot of people (and the wrong chord with a lot of people, so no worries if it doesn’t jive with you).

@media 
  (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.001s !important;
  }
}

@media 
  (prefers-color-scheme: dark) {
  :root {
    --bg: #222;
  }
}

Preference Queries

Preference queries are generally @media queries like we’ve been using to respond to different browsers sizes for year, but now include ways to detect specific user preferences at the OS level. Two examples are prefers-reduced-motion and prefers-color-scheme. These allow us to build interfaces that more closely respect a user’s ideal experience. Read Una’s post.

.block {
  background: 
    hsl(0 33% 53% / 0.5);

  background:
    rgb(255 0 0);

  background:
    /* can display colors 
       no other format can */
    color(display-p3 0.9176 0.2003 0.1386)

  background:
    lab(52.2345% 40.1645 59.9971 / .5);}

  background:
    hwb(194 0% 0% / .5);
}

Color Changes

The color syntax is moving to functions that accept alpha (transparency) without having the change the function name. For example, if you wanted pure blue in the CSS3 days, you might do rgb(0, 0, 255). Today, however, you can do it no-comma style (both work): rgb(0 0 255), and then add alpha without using a different function: rgb(0 0 255 / 0.5). Same exact situation for hsl(). Just a small nicety, and how future color functions will only work.

Speaking of future color syntaxes:

body {
 font-family: 'Recursive', sans-serif;
 font-weight: 950;
 font-variation-settings: 'MONO' 1, 'CASL' 1;
}

Variable Fonts

Web fonts became a big thing in CSS3. Now there are variable fonts. You might as well know they exist. They both unlock some cool design possibilities and can sometimes be good for performance (like no longer needing to load different font files for bold and italic versions of the same font, for example). There is a such thing as color fonts too, but I’d say they haven’t seen much popularity on the web, despite the support.

.cut-out {
  clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
}
.mask {
  mask: url(mask.png) right bottom / 100px repeat-y;
}
.move-me {
  offset-path: path('M 5 5 m -4, 0 a 4,4 0 1,0 8,0 a 4,4 0 1,0 -8,0');
  animation: move 3s linear infinite;
}

@keyframes move {
  100% { 
    offset-distance: 100%;
  }
}

Paths

SVG has also exploded since CSS3. You can straight up crop any element into shapes via clip-path, bringing SVG-like qualities to CSS. Not only that, but you can animate elements along paths, float elements along paths, and even update the paths of SVG elements.

These all feel kind of spirtually connected to me:

  • clip-path — allows us to literally crop elements into shapes.
  • masks — similar to clipping, but a mask can have other qualities like being based on the alpha channel of the mask.
  • offset-path — provides a path that an element can be placed on, generally for the purpose of animating it along that path.
  • shape-outside — provides a path on a floated element that other elements wrap around.
  • d — an SVG’s d attribute on a <path> can be updated via CSS.
.disable {
  filter: 
    blur(1px)
    grayscale(1);
}

.site-header {
  backdrop-filter: 
    blur(10px);
}

.styled-quote {
  mix-blend-mode: 
    exclusion;
} 

CSS Filters

There is a lot of image manipulation (not to mention other DOM elements) that is possible these days directly in CSS. There is quite literally filter, but its got friends and they all have different uses.

These all feel kind of spirtually connected to me:

  • filter — all sorts of useful Photoshop-like effects like brightness, contrast, grayscale, sautration, etc. Blurring is a really unique power.
  • background-blend-mode — again, evocative of Photoshop in how you can blend layers. Multiply the layers to darken and combine. Overlay to mix the background and color. Lighten and darken are classic effects that have real utility in web design, and you never know when a more esoteric lighting effect will create a cool look.
  • backdrop-filter — the same abilities you have with filter, but they only apply to the background and not the entire element. Blurring just the background is a particularly useful effect.
  • mix-blend-mode — the same abilities you have with background-blend-mode, but for the entire element rather than bring limited to backgrounds.
import "https://unpkg.com/extra.css/confetti.js";
body {
  background: paint(extra-confetti);
  height: 100vh;
  margin: 0;
}

Houdini

Houdini is really a collection of technologies that are all essentially based around extending CSS with JavaScript, or at least at the intersection of CSS and JavaScript.

  • Paint API — returns an image that is built from <canvas> APIs and can be controlled through custom properties.
  • Properties & Values API / Typed OM — gives types to values (e.g. 10px) that would have otherwise been strings.
  • Layout API — create your own display properties.
  • Animation API

Combined, these make for some really awesome demos, though browser support is scattered. Part of the magic of Houdini is that it ships as Worklets that are pretty easy to import and use, so it has the potential to modularize powerful functionality while making it trivially easy to use.

my-component {
  --bg: lightgreen;
}

:host(.dark) { 
  background: black; 
}

my-component:part(foo) {
  border-bottom: 2px solid black;
}

Shadow DOM

The Shadow DOM comes up a bit if you’ve played with <svg> and the <use> element. The “cloned” element that comes through has a shadow DOM that has limitations on how you can select “through” it. Then, when you get into <web-components>, it’s the same ball of wax.

If you find yourself needing to style web components, know there are essentially four options from the “outside.” And you might be interested in knowing about native CSS modules and constructible stylesheets.

The CSS Working Group

It’s notable that the CSS working group has its own way of drawing lines in the sand year-to-year, noting where certain specs are at a given point in time:

These are pretty dense though. Sure, they’re great references and document things where we can see what’s changed since CSS3. But no way I’d send a casual front-end developer to these to choose what to learn.

Yeah — but what’s coming?

I’d say probably don’t worry about it. ;)

The point of this is catching up to useful things to know now since the CSS3 era. But if you’re curious about what the future of CSS holds in store…

  • Container queries will be a huge deal. You’ll be able to make styling choices based on the size of a container element rather than the browser size alone. And it’s polyfillable today.
  • Container units will be useful for sizing things based on the size of a container element.
  • Independant transforms, e.g. scale: 1.2;, will feel more logical to use than always having to use transform.
  • Nesting is a feature that all CSS preprocessor have had forever and that developers clearly like using, particularly for media queries. It’s likely we’ll get it in native CSS soon.
  • Scoping will be a way to tell a block of CSS to only apply to a certain area (the same way CSS-in-JS libraries do), and helps with the tricky concept of proximity.
  • Cascade layers open up an entirely new concept of what styles “win” on elements. Styles on higher layers will beat styles on lower layers, regardless of specificity.
  • Viewport units will greatly improve with the introduction of “relative” viewport lengths. The super useful ones will be dvh and dvw, as they factor in the actual usable space in a browser window, preventing terrible issues like the browser UI overlapping a site’s UI.

Bramus Van Damme has a pretty good article covering these things and more in his “CSS in 2022” roundup. It looks like 2022 should be a real banner year for CSS. Perhaps more of a banner year than the CSS3 of 2015.


What should someone learn about CSS if they last boned up during CSS3? originally published on CSS-Tricks. You should get the newsletter and become a supporter.

Useful React Hooks That You Can Use In Your Projects

Hooks are simply functions that allow you to hook into or make use of React features. They were introduced at the React Conf 2018 to address three major problems of class components: wrapper hell, huge components, and confusing classes. Hooks give power to React functional components, making it possible to develop an entire application with it.

The aforementioned problems of class components are connected and solving one without the other could introduce further problems. Thankfully, hooks solved all the problems simply and efficiently while creating room for more interesting features in React. Hooks do not replace already existing React concepts and classes, they merely provide an API to access them directly.

The React team introduced several hooks in React 16.8. However, you could also use hooks from third-party providers in your application or even create a custom hook. In this tutorial, we’ll take a look at some useful hooks in React and how to use them. We’ll go through several code examples of each hook and also explore how you’d create a custom hook.

Note: This tutorial requires a basic understanding of Javascript (ES6+) and React.

Motivation Behind Hooks

As stated earlier, hooks were created to solve three problems: wrapper hell, huge components, and confusing classes. Let’s take a look at each of these in more detail.

Wrapper Hell

Complex applications built with class components easily run into wrapper hell. If you examine the application in the React Dev Tools, you will notice deeply nested components. This makes it very difficult to work with the components or debug them. While these problems could be solved with higher-order components and render props, they require you to modify your code a bit. This could lead to confusion in a complex application.

Hooks are easy to share, you don’t have to modify your components before reusing the logic.

A good example of this is the use of the Redux connect Higher Order Component (HOC) to subscribe to the Redux store. Like all HOCs, to use the connect HOC, you have to export the component alongside the defined higher-order functions. In the case of connect, we’ll have something of this form.

export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

Where mapStateToProps and mapDispatchToProps are functions to be defined.

Whereas in the Hooks era, one can easily achieve the same result neatly and succinctly by using the Redux useSelector and useDispatch hooks.

Huge Components

Class components usually contain side effects and stateful logic. As the application grows in complexity, it is common for the component to become messy and confusing. This is because the side effects are expected to be organized by lifecycle methods rather than functionality. While it is possible to split the components and make them simpler, this often introduces a higher level of abstraction.

Hooks organize side effects by functionality and it is possible to split a component into pieces based on the functionality.

Confusing Classes

Classes are generally a more difficult concept than functions. React class-based components are verbose and a bit difficult for beginners. If you are new to Javascript, you could find functions easier to get started with because of their lightweight syntax as compared to classes. The syntax could be confusing; sometimes, it is possible to forget binding an event handler which could break the code.

React solves this problem with functional components and hooks, allowing developers to focus on the project rather than code syntax.

For instance, the following two React components will yield exactly the same result.

import React, { Component } from "react";
export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      num: 0
    };
    this.incrementNumber = this.incrementNumber.bind(this);
  }
  incrementNumber() {
    this.setState({ num: this.state.num + 1 });
  }
  render() {
    return (
      <div>
        <h1>{this.state.num}</h1>
        <button onClick={this.incrementNumber}>Increment</button>
      </div>
    );
  }
}
import React, { useState } from "react";
export default function App() {
  const [num, setNum] = useState(0);
  function incrementNumber() {
    setNum(num + 1);
  }
  return (
    <div>
      <h1>{num}</h1>
      <button onClick={incrementNumber}>Increment</button>
    </div>
  );
}

The first example is a class-based component while the second is a functional component. Although this is a simple example, notice how bogus the first example is compared to the second.

The Hooks Convention And Rules

Before delving into the various hooks, it could be helpful to take a look at the convention and rules that apply to them. Here are some of the rules that apply to hooks.

  1. The naming convention of hooks should start with the prefix use. So, we can have useState, useEffect, etc. If you are using modern code editors like Atom and VSCode, the ESLint plugin could be a very useful feature for React hooks. The plugin provides useful warnings and hints on the best practices.
  2. Hooks must be called at the top level of a component, before the return statement. They can't be called inside a conditional statement, loop, or nested functions.
  3. Hooks must be called from a React function (inside a React component or another hook). It shouldn’t be called from a Vanilla JS function.
The useState Hook

The useState hook is the most basic and useful React hook. Like other built-in hooks, this hook must be imported from react to be used in our application.

import {useState} from 'react'

To initialize the state, we must declare both the state and its updater function and pass an initial value.

const [state, updaterFn] = useState('')

We are free to call our state and updater function whatever we want but by convention, the first element of the array will be our state while the second element will be the updater function. It is a common practice to prefix our updater function with the prefix set followed by the name of our state in camel case form.

For instance, let’s set a state to hold count values.

const [count, setCount] = useState(0)

Notice that the initial value of our count state is set to 0 and not an empty string. In other words, we can initialize our state to any kind of JavaScript variables, namely number, string, boolean, array, object, and even BigInt. There is a clear difference between setting states with the useState hook and class-based component states. It is noteworthy that the useState hook returns an array, also known as state variables and in the example above, we destructured the array into state and the updater function.

Rerendering Components

Setting states with the useState hook causes the corresponding component to rerender. However, this only happens if React detects a difference between the previous or old state and the new state. React does the state comparison using the Javascript Object.is algorithm.

Setting States With useState

Our count state can be set to new state values by simply passing the new value to the setCount updater function as follows setCount(newValue).

This method works when we don't want to reference the previous state value. If we wish to do that, we need to pass a function to the setCount function.

Assuming we want to add 5 to our count variable anytime a button is clicked, we could do the following.

import {useState} from 'react'

const CountExample = () => {
  // initialize our count state
  const [count, setCount] = useState(0)

  // add 5 to to the count previous state
  const handleClick = () =>{
    setCount(prevCount => prevCount + 5)
  } 
  return(
    <div>
      <h1>{count} </h1>
      <button onClick={handleClick}>Add Five</button>
    </div>
  )
}

export default CountExample

In the code above, we first imported the useState hook from react and then initialized the count state with a default value of 0. We created an onClick handler to increment the value of count by 5 whenever the button is clicked. Then we displayed the result in an h1 tag.

Setting Arrays And Object States

States for arrays and objects can be set in much the same way as other data types. However, if we wish to retain already existing values, we need to use the ES6 spread operator when setting states.

The spread operator in Javascript is used to create a new object from an already existing object. This is useful here because React compares the states with the Object.is operation and then rerender accordingly.

Let’s consider the code below for setting states on button click.

import {useState} from 'react'

const StateExample = () => {
  //initialize our array and object states
  const [arr, setArr] = useState([2, 4])
  const [obj, setObj] = useState({num: 1, name: 'Desmond'})

  // set arr to the new array values
  const handleArrClick = () =>{
    const newArr = [1, 5, 7]
    setArr([...arr, ...newArr])
  } 

  // set obj to the new object values
  const handleObjClick = () =>{
    const newObj = {name: 'Ifeanyi', age: 25}
    setObj({...obj, ...newObj})
  } 

  return(
    <div>
      <button onClick ={handleArrClick}>Set Array State</button>
      <button onClick ={handleObjClick}>Set Object State</button>
    </div>
  )
}

export default StateExample

In the above code, we created two states arr and obj, and initialized them to some array and object values respectively. We then created onClick handlers called handleArrClick and handleObjClick to set the states of the array and object respectively. When handleArrClick fires, we call setArr and use the ES6 spread operator to spread already existing array values and add newArr to it.

We did the same thing for handleObjClick handler. Here we called setObj, spread the existing object values using the ES6 spread operator, and updated the values of name and age.

Async Nature Of useState

As we have already seen, we set states with useState by passing a new value to the updater function. If the updater is called multiple times, the new values will be added to a queue and re-rendering is done accordingly using the JavaScript Object.is comparison.

The states are updated asynchronously. This means that the new state is first added to a pending state and thereafter, the state is updated. So, you may still get the old state value if you access the state immediately it is set.

Let’s consider the following example to observe this behavior.

In the code above, we created a count state using the useState hook. We then created an onClick handler to increment the count state whenever the button is clicked. Observe that although the count state increased, as displayed in the h2 tag, the previous state is still logged in the console. This is due to the async nature of the hook.

If we wish to get the new state, we can handle it in a similar way we would handle async functions. Here is one way to do that.

Here, we stored created newCountValue to store the updated count value and then set the count state with the updated value. Then, we logged the updated count value in the console.

The useEffect Hook

useEffect is another important React hook used in most projects. It does a similar thing to the class-based component’s componentDidMount, componentWillUnmount, and componentDidUpdate lifecycle methods. useEffect provides us an opportunity to write imperative codes that may have side effects on the application. Examples of such effects include logging, subscriptions, mutations, etc.

The user can decide when the useEffect will run, however, if it is not set, the side effects will run on every rendering or rerendering.

Consider the example below.

import {useState, useEffect} from 'react'

const App = () =>{
  const [count, setCount] = useState(0)
  useEffect(() =>{
    console.log(count)
  })

  return(
    <div>
      ...
    </div>
  )
}

In the code above, we simply logged count in the useEffect. This will run after every render of the component.

Sometimes, we may want to run the hook once (on the mount) in our component. We can achieve this by providing a second parameter to useEffect hook.

import {useState, useEffect} from 'react'

const App = () =>{
  const [count, setCount] = useState(0)
  useEffect(() =>{
    setCount(count + 1)
  }, [])

  return(
    <div>
      <h1>{count}</h1>
      ...
    </div>
  )
}

The useEffect hook has two parameters, the first parameter is the function we want to run while the second parameter is an array of dependencies. If the second parameter is not provided, the hook will run continuously.

By passing an empty square bracket to the hook’s second parameter, we instruct React to run the useEffect hook only once, on the mount. This will display the value 1 in the h1 tag because the count will be updated once, from 0 to 1, when the component mounts.

We could also make our side effect run whenever some dependent values change. This can be done by passing these values in the list of dependencies.

For instance, we could make the useEffect to run whenever count changes as follows.

import { useState, useEffect } from "react";
const App = () => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log(count);
  }, [count]);
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};
export default App;

The useEffect above will run when either of these two conditions is met.

  1. On mount — after the component is rendered.
  2. When the value of count changes.

On mount, the console.log expression will run and log count to 0. Once the count is updated, the second condition is met, so the useEffect runs again, this will continue whenever the button is clicked.

Once we provide the second argument to useEffect, it is expected that we pass all the dependencies to it. If you have ESLINT installed, it will show a lint error if any dependency is not passed to the parameter list. This could also make the side effect behave unexpectedly, especially if it depends on the parameters that are not passed.

Cleaning Up The Effect

useEffect also allows us to clean up resources before the component unmounts. This may be necessary to prevent memory leaks and make the application more efficient. To do this, we’d return the clean-up function at the end of the hook.

useEffect(() => {
  console.log('mounted')

  return () => console.log('unmounting... clean up here')
})

The useEffect hook above will log mounted when the component is mounted. Unmounting… clean up here will be logged when the component unmounts. This can happen when the component is removed from the UI.

The clean-up process typically follows the form below.

useEffect(() => {
  //The effect we intend to make
  effect

  //We then return the clean up
  return () => the cleanup/unsubscription
})

While you may not find so many use cases for useEffect subscriptions, it is useful when dealing with subscriptions and timers. Particularly, when dealing with web sockets, you may need to unsubscribe from the network to save resources and improve performance when the component unmounts.

Fetching And Refetching Data With useEffect

One of the commonest use cases of the useEffect hook is fetching and prefetching data from an API.

To illustrate this, we’ll use fake user data I created from JSONPlaceholder to fetch data with the useEffect hook.

import { useEffect, useState } from "react";
import axios from "axios";

export default function App() {
  const [users, setUsers] = useState([]);
  const endPoint =
    "https://my-json-server.typicode.com/ifeanyidike/jsondata/users";

  useEffect(() => {
    const fetchUsers = async () => {
      const { data } = await axios.get(endPoint);
      setUsers(data);
    };
    fetchUsers();
  }, []);

  return (
    <div className="App">
      {users.map((user) => (
            <div>
              <h2>{user.name}</h2>
              <p>Occupation: {user.job}</p>
              <p>Sex: {user.sex}</p>
            </div>
          ))}
    </div>
  );
}

In the code above, we created a users state using the useState hook. Then we fetched data from an API using Axios. This is an asynchronous process, and so we used the async/await function, we could have also used the dot then the syntax. Since we fetched a list of users, we simply mapped through it to display the data.

Notice that we passed an empty parameter to the hook. This ensures that it is called just once when the component mounts.

We can also refetch the data when some conditions change. We’ll show this in the code below.

import { useEffect, useState } from "react";
import axios from "axios";

export default function App() {
  const [userIDs, setUserIDs] = useState([]);
  const [user, setUser] = useState({});
  const [currentID, setCurrentID] = useState(1);

  const endPoint =
    "https://my-json-server.typicode.com/ifeanyidike/userdata/users";

  useEffect(() => {
    axios.get(endPoint).then(({ data }) => setUserIDs(data));
  }, []);

  useEffect(() => {
    const fetchUserIDs = async () => {
      const { data } = await axios.get(`${endPoint}/${currentID}`});
      setUser(data);
    };

    fetchUserIDs();
  }, [currentID]);

  const moveToNextUser = () => {
    setCurrentID((prevId) => (prevId < userIDs.length ? prevId + 1 : prevId));
  };
  const moveToPrevUser = () => {
    setCurrentID((prevId) => (prevId === 1 ? prevId : prevId - 1));
  };
  return (
    <div className="App">
        <div>
          <h2>{user.name}</h2>
          <p>Occupation: {user.job}</p>
          <p>Sex: {user.sex}</p>
        </div>

      <button onClick={moveToPrevUser}>Prev</button>
      <button onClick={moveToNextUser}>Next</button>
    </div>
  );
}

Here we created two useEffect hooks. In the first one, we used the dot then syntax to get all users from our API. This is necessary to determine the number of users.

We then created another useEffect hook to get a user based on the id. This useEffect will refetch the data whenever the id changes. To ensure this, we passed the id in the dependency list.

Next, we created functions to update the value of our id whenever the buttons are clicked. Once the value of the id changes, the useEffect will run again and refetch the data.

If we want, we can even clean up or cancel the promise-based token in Axios, we could do that with the clean-up method discussed above.

useEffect(() => {
    const source = axios.CancelToken.source();
    const fetchUsers = async () => {
      const { data } = await axios.get(`${endPoint}/${num}`, {
        cancelToken: source.token
      });
      setUser(data);
    };
    fetchUsers();

    return () => source.cancel();
  }, [num]);

Here, we passed the Axios’ token as a second parameter to axios.get. When the component unmounts we then canceled the subscription by calling the cancel method of the source object.

The useReducer Hook

The useReducer hook is a very useful React hook that does a similar thing to the useState hook. According to the React documentation, this hook should be used to handle more complex logic than the useState hook. It’s worthy of note that the useState hook is internally implemented with the useReducer hook.

The hook takes a reducer as an argument and can optionally take the initial state and an init function as arguments.

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

Here, init is a function and it is used whenever we want to create the initial state lazily.

Let’s look at how to implement the useReducer hook by creating a simple to-do app as shown in the sandbox below.

First off, we should create our reducer to hold the states.

export const ADD_TODO = "ADD_TODO";
export const REMOVE_TODO = "REMOVE_TODO";
export const COMPLETE_TODO = "COMPLETE_TODO";

const reducer = (state, action) => {
  switch (action.type) {
    case ADD_TODO:
      const newTodo = {
        id: action.id,
        text: action.text,
        completed: false
      };
      return [...state, newTodo];
    case REMOVE_TODO:
      return state.filter((todo) => todo.id !== action.id);
    case COMPLETE_TODO:
      const completeTodo = state.map((todo) => {
        if (todo.id === action.id) {
          return {
            ...todo,
            completed: !todo.completed
          };
        } else {
          return todo;
        }
      });
      return completeTodo;
    default:
      return state;
  }
};
export default reducer;

We created three constants corresponding to our action types. We could have used strings directly but this method is preferable to avoid typos.

Then we created our reducer function. Like in Redux, the reducer must take the state and the action object. But unlike Redux, we don't need to initialize our reducer here.

Furthermore, for a lot of state management use-cases, a useReducer along with the dispatch exposed via context can enable a larger application to fire actions, update state and listen to it.

Then we used the switch statements to check the action type passed by the user. If the action type is ADD_TODO, we want to pass a new to-do and if it is REMOVE_TODO, we want to filter the to-dos and remove the one that corresponds to the id passed by the user. If it is COMPLETE_TODO, we want to map through the to-dos and toggle the one with the id passed by the user.

Here is the App.js file where we implemented the reducer.

import { useReducer, useState } from "react";
import "./styles.css";
import reducer, { ADD_TODO, REMOVE_TODO, COMPLETE_TODO } from "./reducer";
export default function App() {
  const [id, setId] = useState(0);
  const [text, setText] = useState("");
  const initialState = [
    {
      id: id,
      text: "First Item",
      completed: false
    }
  ];

  //We could also pass an empty array as the initial state
  //const initialState = []

  const [state, dispatch] = useReducer(reducer, initialState);
  const addTodoItem = (e) => {
    e.preventDefault();
    const newId = id + 1;
    setId(newId);
    dispatch({
      type: ADD_TODO,
      id: newId,
      text: text
    });
    setText("");
  };
  const removeTodo = (id) => {
    dispatch({ type: REMOVE_TODO, id });
  };
  const completeTodo = (id) => {
    dispatch({ type: COMPLETE_TODO, id });
  };
  return (
    <div className="App">
      <h1>Todo Example</h1>
      <form className="input" onSubmit={addTodoItem}>
        <input value={text} onChange={(e) => setText(e.target.value)} />
        <button disabled={text.length === 0} type="submit">+</button>
      </form>
      <div className="todos">
        {state.map((todo) => (
          <div key={todo.id} className="todoItem">
            <p className={todo.completed && "strikethrough"}>{todo.text}</p>
            <span onClick={() => removeTodo(todo.id)}>✕</span>
            <span onClick={() => completeTodo(todo.id)}>✓</span>
          </div>
        ))}
      </div>
    </div>
  );
}

Here, we created a form containing an input element, to collect the user’s input, and a button to trigger the action. When the form is submitted, we dispatched an action of type ADD_TODO, passing a new id and to-do text. We created a new id by incrementing the previous id value by 1. We then cleared the input text box. To delete and complete to-do, we simply dispatched the appropriate actions. These have already been implemented in the reducer as shown above.

However, the magic happens because we are using the useReducer hook. This hook accepts the reducer and the initial state and returns the state and the dispatch function. Here, the dispatch function serves the same purpose as the setter function for the useState hook and we can call it anything we want instead of dispatch.

To display the to-do items, we simply mapped through the list of to-dos returned in our state object as shown in the code above.

This shows the power of the useReducer hook. We could also achieve this functionality with the useState hook but as you can see from the example above, the useReducer hook helped us to keep things neater. useReducer is often beneficial when the state object is a complex structure and is updated in different ways as against a simple value-replace. Also, once these update functions get more complicated, useReducer makes it easy to hold all that complexity in a reducer function (which is a pure JS function) making it very easy to write tests for the reducer function alone.

We could have also passed the third argument to the useReducer hook to create the initial state lazily. This means that we could calculate the initial state in an init function.

For instance, we could create an init function as follows:

const initFunc = () => [
  {
      id: id,
      text: "First Item",
      completed: false
    }
]

and then pass it to our useReducer hook.

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

If we do this, the initFunc will override the initialState we provided and the initial state will be calculated lazily.

The useContext Hook

The React Context API provides a way to share states or data throughout the React component tree. The API has been available in React, as an experimental feature, for a while but it became safe to use in React 16.3.0. The API makes data sharing between components easy while eliminating prop drilling.

While you can apply the React Context to your entire application, it is also possible to apply it to part of the application.

To use the hook, you need to first create a context using React.createContext and this context can then be passed to the hook.

To demonstrate the use of the useContext hook, let’s create a simple app that will increase font size throughout our application.

Let’s create our context in context.js file.

import { createContext } from "react";

//Here, we set the initial fontSize as 16.
const fontSizeContext = createContext(16);
export default fontSizeContext;

Here, we created a context and passed an initial value of 16 to it, and then exported the context. Next, let’s connect our context to our application.

import FontSizeContext from "./context";
import { useState } from "react";
import PageOne from "./PageOne";
import PageTwo from "./PageTwo";
const App = () => {
  const [size, setSize] = useState(16);
  return (
    <FontSizeContext.Provider value={size}>
      <PageOne />
      <PageTwo />
      <button onClick={() => setSize(size + 5)}>Increase font</button>
      <button
        onClick={() =>
          setSize((prevSize) => Math.min(11, prevSize - 5))
        }
      >
        Decrease font
      </button>
    </FontSizeContext.Provider>
  );
};
export default App;

In the above code, we wrapped our entire component tree with FontSizeContext.Provider and passed size to its value prop. Here, size is a state-created with the useState hook. This allows us to change the value prop whenever the size state changes. By wrapping the entire component with the Provider, we can access the context anywhere in our application.

For instance, we accessed the context in <PageOne /> and <PageTwo />. As a result of this, the font size will increase across these two components when we increase it from the App.js file. We can increase or decrease the font size from the buttons as shown above and once we do, the font size changes throughout the application.

import { useContext } from "react";
import context from "./context";
const PageOne = () => {
  const size = useContext(context);
  return <p style={{ fontSize: `${size}px` }}>Content from the first page</p>;
};
export default PageOne;

Here, we accessed the context using the useContext hook from our PageOne component. We then used this context to set our font-size property. A similar procedure applies to the PageTwo.js file.

Themes or other higher-order app-level configurations are good candidates for contexts.

Using useContext And useReducer

When used with the useReducer hook, useContext allows us to create our own state management system. We can create global states and easily manage them in our application.

Let’s improve our to-do application using the context API.

As usual, we need to create a todoContext in the todoContext.js file.

import { createContext } from "react";
const initialState = [];
export default createContext(initialState);

Here we created the context, passing an initial value of an empty array. Then we exported the context.

Let’s refactor our App.js file by separating the to-do list and items.

import { useReducer, useState } from "react";
import "./styles.css";
import todoReducer, { ADD_TODO } from "./todoReducer";
import TodoContext from "./todoContext";
import TodoList from "./TodoList";

export default function App() {
  const [id, setId] = useState(0);
  const [text, setText] = useState("");
  const initialState = [];
  const [todoState, todoDispatch] = useReducer(todoReducer, initialState);

  const addTodoItem = (e) => {
    e.preventDefault();
    const newId = id + 1;
    setId(newId);
    todoDispatch({
      type: ADD_TODO,
      id: newId,
      text: text
    });
    setText("");
  };
  return (
    <TodoContext.Provider value={[todoState, todoDispatch]}>
        <div className="app">
          <h1>Todo Example</h1>
          <form className="input" onSubmit={addTodoItem}>
            <input value={text} onChange={(e) => setText(e.target.value)} />
            <button disabled={text.length === 0} type="submit">
              +
            </button>
          </form>
          <TodoList />
        </div>
    </TodoContext.Provider>
  );
}

Here, we wrapped our App.js file with the TodoContext.Provider then we passed the return values of our todoReducer to it. This makes the reducer’s state and dispatch function to be accessible throughout our application.

We then separated the to-do display into a component TodoList. We did this without prop drilling, thanks to the Context API. Let’s take a look at the TodoList.js file.

import React, { useContext } from "react";
import TodoContext from "./todoContext";
import Todo from "./Todo";
const TodoList = () => {
  const [state] = useContext(TodoContext);
  return (
    <div className="todos">
      {state.map((todo) => (
        <Todo key={todo.id} todo={todo} />
      ))}
    </div>
  );
};
export default TodoList;

Using array destructuring, we can access the state (leaving the dispatch function) from the context using the useContext hook. We can then map through the state and display the to-do items. We still extracted this in a Todo component. The ES6+ map function requires us to pass a unique key and since we need the specific to-do, we pass it alongside as well.

Let’s take a look at the Todo component.

import React, { useContext } from "react";
import TodoContext from "./todoContext";
import { REMOVE_TODO, COMPLETE_TODO } from "./todoReducer";
const Todo = ({ todo }) => {
  const [, dispatch] = useContext(TodoContext);
  const removeTodo = (id) => {
    dispatch({ type: REMOVE_TODO, id });
  };
  const completeTodo = (id) => {
    dispatch({ type: COMPLETE_TODO, id });
  };
  return (
    <div className="todoItem">
      <p className={todo.completed ? "strikethrough" : "nostrikes"}>
        {todo.text}
      </p>
      <span onClick={() => removeTodo(todo.id)}>✕</span>
      <span onClick={() => completeTodo(todo.id)}>✓</span>
    </div>
  );
};
export default Todo;

Again using array destructuring, we accessed the dispatch function from the context. This allows us to define the completeTodo and removeTodo function as already discussed in the useReducer section. With the todo prop passed from todoList.js we can display a to-do item. We can also mark it as completed and remove the to-do as we deem fit.

It is also possible to nest more than one context provider in the root of our application. This means that we can use more than one context to perform different functions in an application.

To demonstrate this, let’s add theming to the to-do example.

Here’s what we’ll be building.

Again, we have to create themeContext. To do this, create a themeContext.js file and add the following codes.

import { createContext } from "react";
import colors from "./colors";
export default createContext(colors.light);

Here, we created a context and passed colors.light as the initial value. Let’s define the colors with this property in the colors.js file.

const colors = {
  light: {
    backgroundColor: "#fff",
    color: "#000"
  },
  dark: {
    backgroundColor: "#000",
    color: "#fff"
  }
};
export default colors;

In the code above, we created a colors object containing light and dark properties. Each property has backgroundColor and color object.

Next, we create the themeReducer to handle the theme states.

import Colors from "./colors";
export const LIGHT = "LIGHT";
export const DARK = "DARK";
const themeReducer = (state, action) => {
  switch (action.type) {
    case LIGHT:
      return {
        ...Colors.light
      };
    case DARK:
      return {
        ...Colors.dark
      };
    default:
      return state;
  }
};
export default themeReducer;

Like all reducers, the themeReducer takes the state and the action. It then uses the switch statement to determine the current action. If it’s of type LIGHT, we simply assign Colors.light props and if it’s of type DARK, we display Colors.dark props. We could have easily done this with the useState hook but we choose useReducer to drive the point home.

Having set up the themeReducer, we can then integrate it in our App.js file.

import { useReducer, useState, useCallback } from "react";
import "./styles.css";
import todoReducer, { ADD_TODO } from "./todoReducer";
import TodoContext from "./todoContext";
import ThemeContext from "./themeContext";
import TodoList from "./TodoList";
import themeReducer, { DARK, LIGHT } from "./themeReducer";
import Colors from "./colors";
import ThemeToggler from "./ThemeToggler";

const themeSetter = useCallback(
      theme => themeDispatch({type: theme}, 
    [themeDispatch]);

export default function App() {
  const [id, setId] = useState(0);
  const [text, setText] = useState("");
  const initialState = [];
  const [todoState, todoDispatch] = useReducer(todoReducer, initialState);
  const [themeState, themeDispatch] = useReducer(themeReducer, Colors.light);
  const themeSetter = useCallback(
    (theme) => {
      themeDispatch({ type: theme });
    },
    [themeDispatch]
  );
  const addTodoItem = (e) => {
    e.preventDefault();
    const newId = id + 1;
    setId(newId);
    todoDispatch({
      type: ADD_TODO,
      id: newId,
      text: text
    });
    setText("");
  };

  return (
    <TodoContext.Provider value={[todoState, todoDispatch]}>
      <ThemeContext.Provider
        value={[
          themeState,
          themeSetter
        ]}
      >
        <div className="app" style={{ ...themeState }}>
          <ThemeToggler />
          <h1>Todo Example</h1>
          <form className="input" onSubmit={addTodoItem}>
            <input value={text} onChange={(e) => setText(e.target.value)} />
            <button disabled={text.length === 0} type="submit">
              +
            </button>
          </form>
          <TodoList />
        </div>
      </ThemeContext.Provider>
    </TodoContext.Provider>
  );
}

In the above code, we added a few things to our already existing to-do application. We began by importing the ThemeContext, themeReducer, ThemeToggler, and Colors. We created a reducer using the useReducer hook, passing the themeReducer and an initial value of Colors.light to it. This returned the themeState and themeDispatch to us.

We then nested our component with the provider function from the ThemeContext, passing the themeState and the dispatch functions to it. We also added theme styles to it by spreading out the themeStates. This works because the colors object already defined properties similar to what the JSX styles will accept.

However, the actual theme toggling happens in the ThemeToggler component. Let’s take a look at it.

import ThemeContext from "./themeContext";
import { useContext, useState } from "react";
import { DARK, LIGHT } from "./themeReducer";
const ThemeToggler = () => {
  const [showLight, setShowLight] = useState(true);
  const [themeState, themeSetter] = useContext(ThemeContext);
  const dispatchDarkTheme = () => themeSetter(DARK);
  const dispatchLightTheme = () => themeSetter(LIGHT);
  const toggleTheme = () => {
    showLight ? dispatchDarkTheme() : dispatchLightTheme();
    setShowLight(!showLight);
  };
  console.log(themeState);
  return (
    <div>
      <button onClick={toggleTheme}>
        {showLight ? "Change to Dark Theme" : "Change to Light Theme"}
      </button>
    </div>
  );
};
export default ThemeToggler;

In this component, we used the useContext hook to retrieve the values we passed to the ThemeContext.Provider from our App.js file. As shown above, these values include the ThemeState, dispatch function for the light theme, and dispatch function for the dark theme. Thereafter, we simply called the dispatch functions to toggle the themes. We also created a state showLight to determine the current theme. This allows us to easily change the button text depending on the current theme.

The useMemo Hook

The useMemo hook is designed to memoize expensive computations. Memoization simply means caching. It caches the computation result with respect to the dependency values so that when the same values are passed, useMemo will just spit out the already computed value without recomputing it again. This can significantly improve performance when done correctly.

The hook can be used as follows:

const memoizedResult = useMemo(() => expensiveComputation(a, b), [a, b])

Let’s consider three cases of the useMemo hook.

  1. When the dependency values, a and b remain the same.
    The useMemo hook will return the already computed memoized value without recomputation.
  2. When the dependency values, a and b change.
    The hook will recompute the value.
  3. When no dependency value is passed.
    The hook will recompute the value.

Let’s take a look at an example to demonstrate this concept.

In the example below, we’ll be computing the PAYE and Income after PAYE of a company’s employees with fake data from JSONPlaceholder.

The calculation will be based on the personal income tax calculation procedure for Nigeria providers by PricewaterhouseCoopers available here.

This is shown in the sandbox below.

First, we queried the API to get the employees’ data. We also get data for each employee (with respect to their employee id).

const [employee, setEmployee] = useState({});
  const [employees, setEmployees] = useState([]);
  const [num, setNum] = useState(1);
  const endPoint =
    "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees";
  useEffect(() => {
    const getEmployee = async () => {
      const { data } = await axios.get(`${endPoint}/${num}`);
      setEmployee(data);
    };
    getEmployee();
  }, [num]);
  useEffect(() => {
    axios.get(endPoint).then(({ data }) => setEmployees(data));
  }, [num]);

We used axios and the async/await method in the first useEffect and then the dot then syntax in the second. These two approaches work in the same way.

Next, using the employee data we got from above, let’s calculate the relief variables:

const taxVariablesCompute = useMemo(() => {
    const { income, noOfChildren, noOfDependentRelatives } = employee;

    //supposedly complex calculation
    //tax relief computations for relief Allowance, children relief, 
    // relatives relief and pension relief

    const reliefs =
      reliefAllowance1 +
      reliefAllowance2 +
      childrenRelief +
      relativesRelief +
      pensionRelief;
    return reliefs;
  }, [employee]);

This is a fairly complex calculation and so we had to wrap it in a useMemo hook to memoize or optimize it. Memoizing it this way will ensure that the calculation will not be recomputed if we tried to access the same employee again.

Furthermore, using the tax relief values obtained above, we’d like to calculate the PAYE and income after PAYE.

const taxCalculation = useMemo(() => {
    const { income } = employee;
    let taxableIncome = income - taxVariablesCompute;
    let PAYE = 0;

    //supposedly complex calculation
    //computation to compute the PAYE based on the taxable income and tax endpoints

    const netIncome = income - PAYE;
    return { PAYE, netIncome };
  }, [employee, taxVariablesCompute]);

We performed tax calculation (a fairly complex calculation) using the above-computed tax variables and then memoized it with the useMemo hook.

The complete code is available on here.

This follows the tax calculation procedure given here. We first computed the tax relief considering income, number of children, and number of dependent relatives. Then, we multiplied the taxable income by the PIT rates in steps. While the calculation in question is not entirely necessary for this tutorial, it is provided to show us why useMemo may be necessary. This is also a fairly complex calculation and so we may need to memorize it with useMemo as shown above.

After calculating the values, we simply displayed the result.

Note the following about the useMemo hook.

  • useMemo should be used only when it is necessary to optimize the computation. In other words, when recomputation is expensive.
  • It is advisable to first write the calculation without memorization and only memorize it if it is causing performance issues.
  • Unnecessary and irrelevant use of the useMemo hook may even compound the performance issues.
  • Sometimes, too much memoization can also cause performance issues.
The useCallback Hook

useCallback serves the same purpose as useMemo but it returns a memoized callback instead of a memoized value. In other words, useCallback is the same as passing useMemo without a function call.

For instance, consider the following codes below.

import React, {useCallback, useMemo} from 'react'

const MemoizationExample = () => {
  const a = 5
  const b = 7

  const memoResult = useMemo(() => a + b, [a, b])
  const callbackResult = useCallback(a + b, [a, b])

  console.log(memoResult)
  console.log(callbackResult)

  return(
    <div>
      ...
    </div>
  ) 
}

export default MemoizationExample

In the above example, both memoResult and callbackResult will give the same value of 12. Here, useCallback will return a memoized value. However, we could also make it return a memoized callback by passing it as a function.

The useCallback below will return a memoized callback.

...
  const callbackResult = useCallback(() => a + b, [a, b])
...

We can then trigger the callback when an action is performed or in a useEffect hook.

import {useCallback, useEffect} from 'react'
const memoizationExample = () => {
  const a = 5
  const b = 7
  const callbackResult = useCallback(() => a + b, [a, b])
  useEffect(() => {
    const callback = callbackResult()
    console.log(callback)   
  })

  return (
    <div>
      <button onClick= {() => console.log(callbackResult())}>
        Trigger Callback
      </button>
    </div>
  )
} 
export default memoizationExample

In the above code, we defined a callback function using the useCallback hook. We then called the callback in a useEffect hook when the component mounts and also when a button is clicked.

Both the useEffect and the button click yield the same result.

Note that the concepts, do’s, and don’ts that apply to the useMemo hook also apply to the useCallback hook. We can recreate the useMemo example with useCallback.

The useRef Hook

useRef returns an object that can persist in an application. The hook has only one property, current, and we can easily pass an argument to it.

It serves the same purpose a createRef used in class-based components. We can create a reference with this hook as follows:

const newRef = useRef('')

Here we created a new ref called newRef and passed an empty string to it.

This hook is used mainly for two purposes:

  1. Accessing or manipulating the DOM, and
  2. Storing mutable states — this is useful when we don’t want the component to rerender when a value change.

Manipulating the DOM

When passed to a DOM element, the ref object points to that element and can be used to access its DOM attributes and properties.

Here is a very simple example to demonstrate this concept.

import React, {useRef, useEffect} from 'react'

const RefExample = () => {
  const headingRef = useRef('')
  console.log(headingRef)
  return(
    <div>
      <h1 className='topheading' ref={headingRef}>This is a h1 element</h1>
    </div>
  )
}
export default RefExample

In the example above, we defined headingRef using the useRef hook passing an empty string. We then set the ref in the h1 tag by passing ref = {headingRef}. By setting this ref, we have asked the headingRef to point to our h1 element. This means that we can access the properties of our h1 element from the ref.

To see this, if we check the value of console.log(headingRef), we’ll get {current: HTMLHeadingElement} or {current: h1} and we can assess all the properties or attributes of the element. A similar thing applies to any other HTML element.

For instance, we could make the text italic when the component mounts.

useEffect(() => {
  headingRef.current.style.fontStyle = "italic";
}, []);

We can even change the text to something else.

...
    headingRef.current.innerHTML = "A Changed H1 Element";
...

We can even change the background color of the parent container as well.

...
    headingRef.current.parentNode.style.backgroundColor = "red";
...

Any kind of DOM manipulation can be done here. Observe that headingRef.current can be read in the same way as document.querySelector('.topheading').

One interesting use case of the useRef hook in manipulating the DOM element is to focus the cursor on the input element. Let’s quickly run through it.

import {useRef, useEffect} from 'react'

const inputRefExample = () => {
  const inputRef = useRef(null)
  useEffect(() => {
    inputRef.current.focus()
  }, [])

  return(
    <div>
      <input ref={inputRef} />
      <button onClick = {() => inputRef.current.focus()}>Focus on Input </button>
    </div>
  )
}
export default inputRefExample

In the above code, we created inputRef using the useRef hook and then asked it to point to the input element. We then made the cursor focus on the input ref when the component loads and when the button is clicked using inputRef.current.focus(). This is possible because focus() is an attribute of input elements and so the ref will be able to assess the methods.

Refs created in a parent component can be assessed at the child component by forwarding it using React.forwardRef(). Let’s take a look at it.

Let’s first create another component NewInput.js and add the following codes to it.

import { useRef, forwardRef } from "react";
const NewInput = forwardRef((props, ref) => {
  return <input placeholder={props.val} ref={ref} />;
});
export default NewInput;

This component accepts props and ref. We passed the ref to its ref prop and props.val to its placeholder prop. Regular React components do not take a ref attribute. This attribute is available only when we wrap it with React.forwardRef as shown above.

We can then easily call this in the parent component.

...
<NewInput val="Just an example" ref={inputRef} />
...

Storing The Mutable States

Refs are not just used to manipulate DOM elements, they can also be used to store mutable values without re-rendering the entire component.

The following example will detect the number of times a button is clicked without re-rendering the component.

import { useRef } from "react";

export default function App() {
  const countRef = useRef(0);
  const increment = () => {
    countRef.current++;
    console.log(countRef);
  };
  return (
    <div className="App">
      <button onClick={increment}>Increment </button>
    </div>
  );
}

In the code above, we incremented the countRef when the button is clicked and then logged it to the console. Although the value is incremented as shown in the console, we won’t be able to see any change if we try to assess it directly in our component. It will only update in the component when it re-renders.

Note that while useState is asynchronous, useRef is synchronous. In other words, the value is available immediately after it is updated.

The useLayoutEffect Hook

Like the useEffect hook, useLayoutEffect is called after the component is mounted and rendered. This hook fires after DOM mutation and it does so synchronously. Apart from getting called synchronously after DOM mutation, useLayoutEffect does the same thing as useEffect.

useLayoutEffect should only be used for performing DOM mutation or DOM-related measurement, otherwise, you should use the useEffect hook. Using the useEffect hook for DOM mutation functions may cause some performance issues such as flickering but useLayoutEffect handles them perfectly as it runs after the mutations have occurred.

Let’s take a look at some examples to demonstrate this concept.

  1. We’ll be getting the width and height of the window on resize.
import {useState, useLayoutEffect} from 'react'

const ResizeExample = () =>{
  const [windowSize, setWindowSize] = useState({width: 0, height: 0})
  useLayoutEffect(() => {
    const resizeWindow = () => setWindowSize({
      width: window.innerWidth,
      height: window.innerHeight
    })
    window.addEventListener('resize', resizeWindow)
    return () => window.removeEventListener('resize', resizeWindow)
  }, [])

  return (
    <div>
      <p>width: {windowSize.width}</p>
      <p>height: {windowSize.height}</p>
    </div>
  )
}
export default ResizeExample

In the above code, we created a state windowSize with width and height properties. Then we set the state to the current window’s width and height respectively when the window is resized. We also cleaned up the code when it unmounts. The clean-up process is essential in useLayoutEffect to clean up the DOM manipulation and improve efficiency.

  1. Let’s blur a text with useLayoutEffect.
import { useRef, useState, useLayoutEffect } from "react";

export default function App() {
  const paragraphRef = useRef("");

  useLayoutEffect(() => {
    const { current } = paragraphRef;
    const blurredEffect = () => {
      current.style.color = "transparent";
      current.style.textShadow = "0 0 5px rgba(0,0,0,0.5)";
    };
    current.addEventListener("click", blurredEffect);
    return () => current.removeEventListener("click", blurredEffect);
  }, []);

  return (
    <div className="App">
      <p ref={paragraphRef}>This is the text to blur</p>
    </div>
  );
}

We used useRef and useLayoutEffect together in the above code. We first created a ref, paragraphRef to point to our paragraph. Then we created an on-click event listener to monitor when the paragraph is clicked and then blurred it using the style properties we defined. Finally, we cleaned up the event listener using removeEventListener.

The useDispatch And useSelector Hooks

useDispatch is a Redux hook for dispatching (triggering) actions in an application. It takes an action object as an argument and invokes the action. useDispatch is the hook’s equivalence to mapDispatchToProps.

On the other hand, useSelector is a Redux hook for assessing Redux states. It takes a function to select the exact Redux reducer from the store and then returns the corresponding states.

Once our Redux store is connected to a React application through the Redux provider, we can invoke the actions with useDispatch and access the states with useSelector. Every Redux action and state can be assessed with these two hooks.

Note that these states ship with React Redux (a package that makes assessing the Redux store easy in a React application). They are not available in the core Redux library.

These hooks are very simple to use. First, we have to declare the dispatch function and then trigger it.

import {useDispatch, useSelector} from 'react-redux'
import {useEffect} from 'react'
const myaction from '...'

const ReduxHooksExample = () =>{
  const dispatch = useDispatch()
  useEffect(() => {
    dispatch(myaction());
    //alternatively, we can do this
    dispatch({type: 'MY_ACTION_TYPE'})
  }, [])       

  const mystate = useSelector(state => state.myReducerstate)

  return(
    ...
  )
}
export default ReduxHooksExample

In the above code, we imported useDispatch and useSelector from react-redux. Then, in a useEffect hook, we dispatched the action. We could define the action in another file and then call it here or we could define it directly as shown in the useEffect call.

Once we have dispatched the actions, our states will be available. We can then retrieve the state using the useSelector hook as shown. The states can be used in the same way we would use states from the useState hook.

Let’s take a look at an example to demonstrate these two hooks.

To demonstrate this concept, we have to create a Redux store, reducer, and actions. To simplify things here, we’ll be using the Redux Toolkit library with our fake database from JSONPlaceholder.

We need to install the following packages to get started. Run the following bash commands.

npm i redux @reduxjs/toolkit react-redux axios

First, let’s create the employeesSlice.js to handle the reducer and action for our employees’ API.

import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios from "axios";
const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees";

export const fetchEmployees = createAsyncThunk("employees/fetchAll", async () => {
    const { data } = await axios.get(endPoint);
    return data;
});

const employeesSlice = createSlice({
  name: "employees",
  initialState: { employees: [], loading: false, error: "" },
  reducers: {},
  extraReducers: {
    [fetchEmployees.pending]: (state, action) => {
      state.status = "loading";
    },
    [fetchEmployees.fulfilled]: (state, action) => {
      state.status = "success";
      state.employees = action.payload;
    },
    [fetchEmployees.rejected]: (state, action) => {
      state.status = "error";
      state.error = action.error.message;
    }
  }
});
export default employeesSlice.reducer;

This is the standard setup for the Redux toolkit. We used the createAsyncThunk to access the Thunk middleware to perform async actions. This allowed us to fetch the list of employees from the API. We then created the employeesSlice and returned, “loading”, “error”, and the employees’ data depending on the action types.

Redux toolkit also makes setting up the store easy. Here is the store.

import { configureStore } from "@reduxjs/toolkit";
import { combineReducers } from "redux";
import employeesReducer from "./employeesSlice";

const reducer = combineReducers({
  employees: employeesReducer
});

export default configureStore({ reducer });;

Here, we used combineReducers to bundle the reducers and the configureStore function provided by Redux toolkit to set up the store.

Let’s proceed to use this in our application.

First, we need to connect Redux to our React application. Ideally, this should be done at the root of our application. I like to do it in the index.js file.

import React, { StrictMode } from "react";
import ReactDOM from "react-dom";
import store from "./redux/store";
import { Provider } from "react-redux";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <StrictMode>
      <App />
    </StrictMode>
  </Provider>,
  rootElement
);

Here, I’ve imported the store I created above and also Provider from react-redux.

Then, I wrapped the entire application with the Provider function, passing the store to it. This makes the store accessible throughout our application.

We can then proceed to use the useDispatch and useSelector hooks to fetch the data.

Let’s do this in our App.js file.

import { useDispatch, useSelector } from "react-redux";
import { fetchEmployees } from "./redux/employeesSlice";
import { useEffect } from "react";

export default function App() {
  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(fetchEmployees());
  }, [dispatch]);
  const employeesState = useSelector((state) => state.employees);
  const { employees, loading, error } = employeesState;

  return (
    <div className="App">
      {loading ? (
        "Loading..."
      ) : error ? (
        <div>{error}</div>
      ) : (
        <>
          <h1>List of Employees</h1>
          {employees.map((employee) => (
            <div key={employee.id}>
              <h3>{`${employee.firstName} ${employee.lastName}`}</h3>
            </div>
          ))}
        </>
      )}
    </div>
  );
}

In the above code, we used the useDispatch hook to invoke the fetchEmployees action created in the employeesSlice.js file. This makes the employees state to be available in our application. Then, we used the useSelector hook to get the states. Thereafter, we displayed the results by mapping through the employees.

The useHistory Hook

Navigation is very important in a React application. While you could achieve this in a couple of ways, React Router provides a simple, efficient and popular way to achieve dynamic routing in a React application. Furthermore, React Router provides a couple of hooks for assessing the state of the router and performing navigation on the browser but to use them, you need to first set up your application properly.

To use any React Router hook, we should first wrap our application with BrowserRouter. We can then nest the routes with Switch and Route.

But first, we have to install the package by running the following commands.

npm install react-router-dom

Then, we need to set up our application as follows. I like to do this in my App.js file.

import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Employees from "./components/Employees";
export default function App() {
  return (
    <div className="App">
      <Router>
        <Switch>
          <Route path='/'>
            <Employees />
          </Route>
          ...
        </Switch>
      </Router>
    </div>
  );
}

We could have as many Routes as possible depending on the number of components we wish to render. Here, we have rendered only the Employees component. The path attribute tells React Router DOM the path of the component and can be assessed with query string or various other methods.

The order matters here. The root route should be placed below the child route and so forth. To override this order, you need to include the exact keyword on the root route.

<Route path='/' exact >
  <Employees />
</Route>

Now that we have set up the router, we can then use the useHistory hook and other React Router hooks in our application.

To use the useHistory hook, we need to first declare it as follows.

import {useHistory} from 'history'
import {useHistory} from 'react-router-dom'

const Employees = () =>{
  const history = useHistory()
  ...
}

If we log history to the console, we’ll see several properties associated with it. These include block, createHref, go, goBack, goForward, length, listen, location, push, replace. While all these properties are useful, you will most likely use history.push and history.replace more often than other properties.

Let’s use this property to move from one page to another.

Assuming we want to fetch data about a particular employee when we click on their names. We can use the useHistory hook to navigate to the new page where the employee’s information will be displayed.

function moveToPage = (id) =>{
  history.push(`/employees/${id}`)
}

We can implement this in our Employee.js file by adding the following.

import { useEffect } from "react";
import { Link, useHistory, useLocation } from "react-router-dom";

export default function Employees() {
  const history = useHistory();

  function pushToPage = (id) => {
    history.push(`/employees/${id}`)
  }
  ...
  return (
    <div>
     ...
        <h1>List of Employees</h1>
        {employees.map((employee) => (
          <div key={employee.id}>
            <span>{`${employee.firstName} ${employee.lastName} `}</span>
            <button onClick={pushToPage(employee.id)}> » </button>
          </div>
        ))}
  </div>
  );
}

In the pushToPage function, we used history from the useHistory hook to navigate to the employee’s page and pass the employee id alongside.

The useLocation Hook

This hook also ships with React Router DOM. It is a very popular hook used to work with the query string parameter. This hook is similar to the window.location in the browser.

import {useLocation} from 'react'

const LocationExample = () =>{
  const location = useLocation()
  return (
    ...
  )
}
export default LocationExample

The useLocation hook returns the pathname, search parameter, hash and state. The most commonly used parameters include the pathname and search but you could equally use hash, and state a lot in your application.

The location pathname property will return the path we set in our Route set up. While search will return the query search parameter if any. For instance, if we pass 'http://mywebsite.com/employee/?id=1' to our query, the pathname would be /employee and the search would be ?id=1.

We can then retrieve the various search parameters using packages like query-string or by coding them.

The useParams Hook

If we set up our Route with a URL parameter in its path attribute, we can assess those parameters as key/value pairs with the useParams hook.

For instance, let’s assume that we have the following Route.

<Route path='/employees/:id' >
  <Employees />
</Route>

The Route will be expecting a dynamic id in place of :id.

With the useParams hook, we can assess the id passed by the user, if any.

For instance, assuming the user passes the following in function with history.push,

function goToPage = () => {
  history.push(`/employee/3`)
}

We can use the useParams hook to access this URL parameter as follows.

import {useParams} from 'react-router-dom'

const ParamsExample = () =>{
  const params = useParams()
  console.log(params)  

  return(
    <div>
      ...
    </div>
  )
}
export default ParamsExample

If we log params to the console, we’ll get the following object {id: "3"}.

The useRouteMatch Hook

This hook provides access to the match object. It returns the closest match to a component if no argument is supplied to it.

The match object returns several parameters including the path (the same as the path specified in Route), the URL, params object, and isExact.

For instance, we can use useRouteMatch to return components based on the route.

import { useRouteMatch } from "react-router-dom";
import Employees from "...";
import Admin from "..."

const CustomRoute = () => {
  const match = useRouteMatch("/employees/:id");
  return match ? (
    <Employee /> 
  ) : (
    <Admin />
  );
};
export default CustomRoute;

In the above code, we set a route’s path with useRouteMatch and then rendered the <Employee /> or <Admin /> component depending on the route selected by the user.

For this to work, we still need to add the route to our App.js file.

...
  <Route>
    <CustomRoute />
  </Route>
...
Building A Custom Hook

According to the React documentation, building a custom hook allows us to extract a logic into a reusable function. However, you need to make sure that all the rules that apply to React hooks apply to your custom hook. Check the rules of React hook at the top of this tutorial and ensure that your custom hook complies with each of them.

Custom hooks allow us to write functions once and reuse them whenever they are needed and hence obeying the DRY principle.

For instance, we could create a custom hook to get the scroll position on our page as follows.

import { useLayoutEffect, useState } from "react";

export const useScrollPos = () => {
  const [scrollPos, setScrollPos] = useState({
    x: 0,
    y: 0
  });
  useLayoutEffect(() => {
    const getScrollPos = () =>
      setScrollPos({
        x: window.pageXOffset,
        y: window.pageYOffset
      });
    window.addEventListener("scroll", getScrollPos);
    return () => window.removeEventListener("scroll", getScrollPos);
  }, []);
  return scrollPos;
};

Here, we defined a custom hook to determine the scroll position on a page. To achieve this, we first created a state, scrollPos, to store the scroll position. Since this will be modifying the DOM, we need to use useLayoutEffect instead of useEffect. We added a scroll event listener to capture the x and y scroll positions and then cleaned up the event listener. Finally, we returned to the scroll position.

We can use this custom hook anywhere in our application by calling it and using it just as we would use any other state.

import {useScrollPos} from './Scroll'

const App = () =>{
  const scrollPos = useScrollPos()
  console.log(scrollPos.x, scrollPos.y)
  return (
    ...
  )
}
export default App

Here, we imported the custom hook useScrollPos we created above. Then we initialized it and then logged the value to our console. If we scroll on the page, the hook will show us the scroll position at every step of the scroll.

We can create custom hooks to do just about anything we can imagine in our app. As you can see, we simply need to use the inbuilt React hook to perform some functions. We can also use third-party libraries to create custom hooks but if we do so, we will have to install that library to be able to use the hook.

Conclusion

In this tutorial, we took a good look at some useful React hooks you will be using in most of your applications. We examined what they present and how to use them in your application. We also looked at several code examples to help you understand these hooks and apply them to your application.

I encourage you to try these hooks in your own application to understand them more.

Resources From The React Docs

Composable CSS Animation In Vue With AnimXYZ

In this article, you will learn how to use the AnimXYZ toolkit to create unique, interactive, and visually engaging animations in Vue.js and plain HTML. By the end of this article, you will have learned how adding a few CSS classes to elements in Vue.js components can give you a lot of control over how those elements move in the DOM.

This tutorial will be beneficial to readers who are interested in creating interactive animations with few lines of code.

Note: This article requires a basic understanding of Vue.js and CSS.

What Is AnimXYZ?

AnimXYZ is a composable, performant, and customizable CSS animation toolkit powered by CSS variables. It is designed to enable you to create awesome and unique animations without writing a line of CSS keyframes. Under the hood, it uses CSS variables to create custom CSS properties. The nice thing about AnymXYZ is its declarative approach. An element can be animated in one of two ways: when entering or leaving the page. If you want to animate an HTML element with this toolkit, adding a class of xyz-out will animate the item out of the page, while xyz-in will animate the component into the page.

This awesome toolkit can be used in a regular HTML project, as well as in a Vue.js or React app. However, as of the time of writing, support for React is still under development.

Why Use AnimXYZ?

Composable

Animation with AnimXYZ is possible by adding descriptive class names to your markup. This makes it easy to write complex CSS animation without writing complex CSS keyframes. Animating an element into the page is as easy as adding a class of xyz-in in the component and declaring a descriptive attribute.

<p class="xyz-in" xyz="fade">Composable CSS animation with AnimXYZ</p>

The code above will make the paragraph element fade into the page, while the code below will make the element fade out of the page. Just a single class with a lot of power.

<p class="intro xyz-out" xyz="fade">Composable CSS animation with AnimXYZ</p>

Customizable

For simple animations, you can use the out-of-the-box utilities, but AnimXYZ can do so much more. You can customize and control AnimXYZ to create exactly the animations you want by setting the CSS variables that drive all AnimXYZ animations. We will create some custom animations later on in this tutorial.

Performant

With AnimXYZ, you can create powerful and smooth animations out of the box, and its size is only 2.68 KB for the base functionality and 11.4 KB if you include the convenient utilities.

Easy to Learn and Use

AnimXYZ works perfectly with regular HTML and CSS, and it can be integrated in a project using the content delivery network (CDN) link. It can also be used in Vue.js and React, although support for React is still under development. Also, the learning curve with this toolkit is not steep when compared to animation libraries such as GSAP and Framer Motion, and the official documentation makes it easy to get started because it explains how the package works in simple terms.

Key Concepts in AnimXYZ

Contexts

When you want a particular flow of animation to be applied to related groups of element, the xyz attribute provides the context. Let’s say you want three divs to be animated in the same way when they enter the page. All you have to do is add the xyz attribute to the parent element, with the composable utilities and variable that you want to apply.

<div class="shape-wrapper xyz-in" xyz="fade flip-up flip-left">
  <div class="shape"></div>
  <div class="shape"></div>
  <div class="shape"></div>
</div>

The code above will apply the same animation to all divs with a class of shape. All child elements will fade into the page and flip to the upper left, because the attribute xyz="fade flip-up flip-left" has been applied to the parent element.

See the Pen Contexts in AnimXYZ by Ejiro Asiuwhu.

AnimXYZ makes it easy to animate a child element differently from its parent. To achieve this, add the xyz attribute with a different animation variable and different utilities to the child element, which will reset all animation properties that it has inherited from its parent.

See the Pen Override Parent contexts in AnimXYZ by Ejiro Asiuwhu.

Utilities

AnimXYZ comes with a lot of utilities that will enable you to create engaging and powerful CSS animations without writing any custom CSS.

xyz="fade up in-left in-rotate-left out-right out-rotate-right"

For example, the code above has a fade up utility, which will make the element fade from top to bottom when coming into the page. It will come in and rotate from the left. When the element leaves the page, it will go to the right and rotate out of the page.

With the out-of-the-box utilities, you can, say, flip a group of elements to the right and make them fade while leaving the page. The possibilities of what can be achieved with the utilities are endless.

Staggering

The stagger utility controls the animation-delay CSS property for each of the elements in a list, so that their animations are triggered one after another. It specifies the amount of time to wait between applying the animation to an element and beginning to perform the animation. Essentially, it is used to queue up animation so that elements can be animated in sequence.

<div class="shape-wrapper" xyz="fade up-100% origin-top flip-down flip-right-50% rotate-left-100% stagger">
  <div class="shape xyz-in"></div>
  <div class="shape xyz-in"></div>
  <div class="shape xyz-in"></div>
  <div class="shape xyz-in"></div>
</div>

By adding the stagger utility, each element in a parent div will animate one after another from left to right. The order can be revered by using stagger-rev.

With stagger:

See the Pen Staggering with AnimXYZ by Ejiro Asiuwhu.

Without stagger:

See the Pen !Staggering Animation - AnimXYZ by Ejiro Asiuwhu.

Using AnimXYZ With HTML and CSS

Let’s build a card and add some cool animation with AnimeXYZ.

See the Pen Animxyz Demo by Ejiro Asiuwhu.

First, we need to add the AnimXYZ toolkit to our project. The easiest way is via a CDN. Grab the CDN, and add it to the head of your HTML document.

Add the following lines of code to your HTML.

// html

 <p class="intro xyz-in" xyz="fade">Composable CSS Animation with Animxyz</p>
  <div class="glass xyz-in" id="glass"
        xyz="fade flip-down flip-right-50%  duration-10">
        <img src="https://cdn.dribbble.com/users/238864/screenshots/15043450/media/7160a9f4acc18f4ec2cbe38eb167de62.jpg"
            alt="" class="avatar xyz-in">
        <p class="des xyz-in">Image by Jordon Cheung</p>
  </div>

This is where the magic happens. At the top of the page, we have a paragraph tag with a class of xyz-in and an xyz attribute with a value of fade. This means that the p element will fade into the page.

Next, we have a card with an id of glass, with the following xyz attribute:

  xyz="fade flip-down flip-right-50%  duration-10"

The composable utilities above will make the card fade into the page. The flip-down value will set the card to flip into the page from the bottom, and the flip-right value will flip the card by 50% when leaving the page. An animation duration of 10 (i.e. 1 second) sets the length of time that the animation will take to complete one cycle.

Integrating AnimXYZ in Vue.js

Scaffold a Vue.js Project

Using the Vue.js command-line interface (CLI), run the command below to generate the application:

vue create animxyz-vue

Install VueAnimXYZ

npm install @animxyz/vue

This will install both the core package and the Vue.js package. After installation, we will have to import the VueAnimXYZ package into our project and add the plugin globally to our Vue.js application. To do this, open your main.js file, and add the following block of code accordingly:

import VueAnimXYZ from '@animxyz/vue'  // import AnimXZY vue package
import '@animxyz/core' // import AnimXZY core package

Vue.use(VueAnimXYZ)

The XyzTransition Component

The XyzTransition component is built on top of Vue.js’ transition component. It’s used to animate individual elements into and out of the page.

Here is a demo of how to use the XyzTransition component in Vue.js.


Notice that a lot of the complexity that comes with Vue.js’ transition component has been abstracted away in order to reduce complexity and increase efficiency. All we need to care about when using the XyzTransition component are the appear, appear-visible, duration, and mode props. For a more detailed guide, check out the official documentation. Let’s use the XYZTransition component to animate an element when a button is clicked.
<div id="app">
    <button @click="isAnimate = !isAnimate">Animate</button>
    <XyzTransition
      appear
      xyz="fade up in-left in-rotate-left out-right out-rotate-right"
    >
      <div class="square" v-if="isAnimate"></div>
    </XyzTransition>
</div>
Notice how the element that we intend to transition is wrapped in the XYZTransition component. This is important because the child element <div class="square" v-if="isAnimate"></div> will inherit the utilities that are applied to the XYZTransition component. The child element is also conditionally rendered when isAnimate is set to true. When the button is clicked, the child element with a class of square is toggled into and out of the DOM. #### XyzTransitionGroup The XyzTransitionGroup component is built on top of Vue.js’ transition-group component. It is used to animate groups of elements into and out of the page. Below is an illustration of how to use the XyzTransitionGroup component in Vue.js. Notice here again that a lot of the complexity that comes with Vue.js’ transition-group component has been abstracted away in order to reduce complexity and increase efficiency. All we need to care about when using the XyzTransitionGroup component are appear, appear-visible, duration, and tag. The following is taken from the documentation:
<XyzTransitionGroup
  appear={ boolean }
  appear-visible={ boolean | IntersectionObserverOptions }
        duration={ number | 'auto' | { appear: number | 'auto', in: number | 'auto',
                   out: number | 'auto' } }
        tag={ string } >
        <child-component />
        <child-component />
        <child-component />
</XyzTransitionGroup>
### Build an Animated Modal With AnimXYZ and Vue.js Let’s build modal components that will animate as they enter and leave the DOM. Here is a demo of what we are going to build: Demo By adding the xyz="fade out-delay-5" property to the XyzTransition component, the modal will fade. Notice that we are adding .xyz-nested to almost all child elements of the modal component. This is because we want to trigger their animations when a modal component’s element is open. The ease-out-back property that we added to the dialog container will add a slight overshoot when the dialog is opened and when it has been closed. Adding in-delay to the child elements of the modal component will make the animation feel more natural, because the element will be delayed until the other contents of the modal have animated in:
  <section class="xyz-animate">
    <div class="alerts__wrap copy-content">
      <div class="alert reduced-motion-alert">
        <p>
          AnimXYZ animations are disabled if your browser or OS has
          reduced-motion setting turned on.
          <a href="https://web.dev/prefers-reduced-motion/" target="_blank">
            Learn more here.
          </a>
        </p>
      </div>
    </div>
    <h1>Modal Animation With AnimXYZ and Vue.js</h1>
    <button
      class="modal-toggle modal-btn-main"
      data-modal-text="Open Modal"
      data-modal-title="Title"
      data-modal-close-text="Close"
      id="label_modal_kdf8e87cga"
      aria-haspopup="dialog"
      ref="openButton"
      @click="open"
      autofocus
    >
      Open Modal
    </button>
    <span
      id="js-modal-overlay"
      class="simple-modal-overlay"
      data-background-click="enabled"
      title="Close this window"
      v-if="isModal"
      @click="close"
    >
      <span class="invisible">Close this window</span>
    </span>
    <div
      role="dialog"
      class="simple-modal__wrapper"
      aria-labelledby="modal-title"
    >
      <XyzTransition duration="auto" xyz="fade out-delay-5">
        <section
          id="modal1"
          aria-labelledby="modal1_label"
          aria-modal="true"
          class="modal xyz-nested"
          xyz="fade small stagger ease-out-back"
          v-if="isModal"
          tabindex="-1"
          ref="modal"
          @keydown.esc="close"
        >
          <div class="modal_top flex xyz-nested" xyz="up-100% in-delay-3">
            <header
              id="modal1_label modal-title"
              class="modal_label xyz-nested"
              xyz="fade right in-delay-7"
            >
              Join our community on Slack
            </header>
            <button
              type="button"
              aria-label="Close"
              xyz="fade small in-delay-7"
              class="xyz-nested"
              @click="close"
              title="Close"
            >
              <svg viewBox="0 0 24 24" focusable="false" aria-hidden="true">
                <path
                  fill="currentColor"
                  d="M.439,21.44a1.5,1.5,0,0,0,2.122,2.121L11.823,14.3a.25.25,0,0,1,.354,0l9.262,9.263a1.5,1.5,0,1,0,2.122-2.121L14.3,12.177a.25.25,0,0,1,0-.354l9.263-9.262A1.5,1.5,0,0,0,21.439.44L12.177,9.7a.25.25,0,0,1-.354,0L2.561.44A1.5,1.5,0,0,0,.439,2.561L9.7,11.823a.25.25,0,0,1,0,.354Z"
                ></path>
              </svg>
            </button>
          </div>
          <div class="modal_body xyz-nested" xyz="up-100% in-delay-3">
            <div class="modal_body--top flex justify_center align_center">
              <img
                src="../assets/slack.png"
                alt="slack logo"
                class="slack_logo"
              />
              <img src="../assets/plus.png" alt="plus" class="plus" />
              <img
                src="../assets/discord.png"
                alt="discord logo"
                class="discord_logo"
              />
            </div>
            <p><span class="bold">929</span> users are registered so far.</p>
          </div>
          <form class="modal_form" autocomplete>
            <label for="email"
              ><span class="sr-only">Enter your email</span></label
            >
            <input
              id="email"
              type="email"
              placeholder="johndoe@email.com"
              autocomplete="email"
              aria-describedby="email"
              class="modal_input"
              required
            />
            <button type="submit" class="modal_invite_btn">
              Get my invite
            </button>
            <p>Already joined?</p>
            <button
              type="button"
              aria-describedby="open_slack"
              class="
                modal_slack_btn
                flex
                align_center
                justify_center
                xyz-nested
              "
              xyz="fade in-right in-delay-7"
              id="open_slack"
            >
              <span
                ><img src="../assets/slack.png" alt="slack logo" role="icon"
              /></span>
              Open Slack
            </button>
          </form>
        </section>
      </XyzTransition>
    </div>
  </section>
Then, in our modal, we would use the v-if="isModal" directive to specify that we want the modal to be hidden from the page by default. Then, when the button is clicked, we open the modal by calling the open() method, which sets the isModal property to true. This will reveal the modal on the page and also apply the animation properties that we specified using AnimXYZ’s built-in utilities.
<script>
export default {
  data() {
    return {
      isModal: false,
    };
  },
  methods: {
    open() {
      if (!this.isModal) {
        this.isModal = true;
        this.$nextTick(() => {
          const modalRef = this.$refs.modal;
          console.log(modalRef);
          modalRef.focus();
        });
      }
    },
    close() {
      if (this.isModal) {
        this.isModal = false;
        this.$nextTick(() => {
          const openButtonRef = this.$refs.openButton;
          openButtonRef.focus();
        });
      }
    },
  },
};
</script>
AnimXYZ’s animations are disabled when the reduced-motion setting in the browser or operating system is turned on. Let’s display a helper message to users who have opted in to reduced motion. Using the @media screen and (prefers-reduced-motion) media query, we’ll display a message notifying those users that they’ve turned off the animation feature in our modal component. To do this, add the following block of code to your styles:
<style>
@media (prefers-reduced-motion: reduce) {
  .alerts__wrap {
    display: block;
  }
}
</style>
AnimXYZ animations when reduced-motion setting is turned on.

Conclusion

We’ve gone through the basics of AnimXYZ and how to use it with plain HTML and Vue.js. We’ve also implemented some demo projects that give us a glimpse of the range of CSS animations that we can create simply by adding the composable utility classes provided by this toolkit, and all without writing a single line of a CSS keyframe. Hopefully, this tutorial has given you a solid foundation to add some sleek CSS animations to your own projects and to build on them over time for any of your needs.

The final demo is on GitHub. Feel free to clone it and try out the toolkit for yourself.

That’s all for now! Let me know in the comments section below what you think of this article. I am active on Twitter and GitHub. Thank you for reading, and stay tuned.

Resources

How to Implement and Style the Dialog Element

A dialog is a component of web interfaces providing important information and requiring user interaction in response. The user’s attention should be focused exclusively on the active dialog element, both visually and interactively. Information and user actions presented by the dialog should be phrased simply and unambiguously. So, a dialog is interruptive by nature and should be used sparingly.

Usage

Imagine a user browsing a web application from a mobile phone. The application needs the user’s decision about its settings immediately in order to keep functioning properly – like enabling location services in order to give directions on a map. This could be a use case for a dialog:

  1. The dialog pops up. Only the dialog is interactive, lying over the rest of the content.
  2. The dialog’s header explains the required actions in short. The dialog’s body may contain more detailed information.
  3. One or more interactive elements provide possible user actions in order to find a solution.

Structure

A modal dialog consists of a container, title, description, buttons and a backdrop. If the user’s flow browsing the application must be interrupted anyway, the least we can do is to present the user a concise and well structured, clear dialog to attract their focus and quickly make an action in order to continue browsing.

The basic structure of a modal dialog element.
Basic structure of a modal dialog

It’s essential to phrase a clear and unambiguous message in the title, so the reader can understand it at a glance. Here is one example:

  • Not such a good title: “Do you want to proceed?”
  • Better, because direct and clear: “Allow access to the file system?”

Of course, further information can be put in the body of the dialog itself, but the gist should be comprehensible by reading the title and button texts only.

Behavior

A dialog always needs to suit a notable purpose: Getting the user to make a choice in order to finish a task or to keep the application functioning properly (like enabling location services for navigation).

Should the dialog close by clicking the backdrop?

Well, I only asked myself this question after trying to implement that behavior with the native dialog element. As it turns out, it’s far easier with ordinary divs to achieve.

Without the native dialog element, your markup would look something like this:

<div id="dialog" role="dialog" aria-modal="true">
  <!-- Your dialog content -->
</div>
<div class="backdrop"></div>

And the corresponding CSS

.backdrop {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}

Here we have an ordinary div stretching out over the full viewport. We can easily grab that div.backdrop with JavaScript and implement our “close-modal-on-click-behavior”.

const backdrop = document.querySelector(".backdrop");
const dialog = document.getElementById("dialog");

backdrop.addEventListener("click", function() { dialog.style.display = none; });

So, why can’t we do exactly this with the native dialog element?

The native dialog element comes with a pseudo-element called ::backdrop when invoked with dialog.showModal(). As the name suggests, it is not part of the DOM, and so we cannot access it using JavaScript…

How can we add an event listener on an element, which is essentially not part of the DOM? Well, there are workarounds, like detecting a click outside of the active dialog, but that’s a completely different story.

And once I’ve come to understand, that it is not that easy, I’ve revisitied the initially posed question: Is it worthwhile to close the dialog on click outside?

No, it is not. Keep in mind, that we wanted the user to make a decision. We interrupted the user’s flow of browsing the application. We phrased the message clearly and directly, so that it’ll be comprehensible at a glance. And then we allow the user to dismiss everything we have carefully put together with a single click?! I don’t think so.

Implementation

When we implement a dialog, the following requirements must be observed carefully:

  • Focus the first interactive element inside the modal once the dialog opens (*)
  • Trap focus inside the modal dialog
  • Provide at least one button that closes the dialog
  • Prevent interaction with the rest of the page (*)
  • Close the dialog when the user presses ESC (*)
  • Return focus to the element, that opened the dialog in the first place

Requirements marked with (*) are handled by the native dialog element out of the box, when opened as a modal.

So in order to get all the benefits listed above, we’re going to invoke the dialog using the method showModal provided to us by the native dialog JavaScript API.

// Open dialog as a modal
const dialog = querySelector("dialog");
dialog.showModal(); 

Example HTML structure

<button id="open_dialog">Open Dialog</button>

<dialog
  aria-labelledby="dialog_title"
  aria-describedby="dialog_description"
>
  <img
    src="./location-service.svg"
    alt="Illustration of Location Services"
  />
  <h2 id="dialog_title" class="h2">Use location services?</h2>
  <p id="dialog_description">
    In order to give directional instructions, we kindly ask you to turn
    on the location services.
  </p>
  <div class="flex flex-space-between">
    <button id="close_dialog">Close</button>
    <button id="confirm_dialog" class="cta">Confirm</button>
  </div>
</dialog>

Because we’re using the native dialog element here, we do not need to use role="dialog", modal="true" or similar for an accessible implementation.

Based on this simple HTML structure, which is taken from the example CodePen shown at the end of this article, we can now go ahead and implement the requirements listed above. Once the reader clicks the “Open Dialog” button, the first interactive element inside the dialog will receive focus by default.

Return focus to last active element after closing the dialog

The HTML of a modal dialog can be placed nearly anywhere in the page’s markup. So, when the reader opens the modal, the user agent jumps to the dialog’s markup, like using a portal. Once the reader closes the dialog again, the focus needs to be returned back to the element that the reader was interacting with before opening the dialog. The portal to and from the dialog should go two-way, otherwise the reader will get lost.

const dialog = document.querySelector("dialog");
const openDialogBtn = document.getElementById("open_dialog");
const closeDialogBtn = document.getElementById("close_dialog");

const openDialog = () => {
  dialog.showModal();
};

const closeDialog = () => {
  dialog.close();

  // Returns focus back to the button
  // that opened the dialog
  openDialogBtn.focus();
};

openDialogBtn.addEventListener("click", openDialog);
closeDialogBtn.addEventListener("click", closeDialog);

// If the buttons of the dialog are contained inside a <form>
// Use event.preventDefault()
const closeDialog = (event) => {
  event.preventDefault();
  dialog.close();
  openDialogBtn.focus();
};

Trap focus inside the dialog while open

A focus trap is often a horror regarding UX – in case of a modal dialog it serves an essential purpose: Keeping the reader’s focus on the dialog, helping to prevent interaction with the background.

Based on the same markup and existing JS above, we can add the focus trap to our script.

const trapFocus = (e) => {
  if (e.key === "Tab") {
    const tabForwards = !e.shiftKey && document.activeElement === lastElement;
    const tabBackwards = e.shiftKey && document.activeElement === firstElement;

    if (tabForwards) {
      // only TAB is pressed, not SHIFT simultaneously
      // Prevent default behavior of keydown on TAB (i.e. focus next element)
      e.preventDefault();
      firstElement.focus();
    } else if (tabBackwards) {
      // TAB and SHIFT are pressed simultaneously
      e.preventDefault();
      lastElement.focus();
    }
  }
};

// Attach trapFocus function to dialog on keydown
// Updated openDialog
const openDialog = () => {
  dialog.showModal();
  dialog.addEventListener("keydown", trapFocus);
};

// Remove trapFocus once dialog closes
// Updated closeDialog
const closeDialog = () => {
  dialog.removeEventListener("keydown", trapFocus);
  dialog.close();
  openDialogBtn.focus();
};

Disable closing the dialog on ESC

Just in case you want to disable the built-in functionality of closing the dialog once the user has pressed the ESC key, you can listen for the keydown event when the dialog opens and prevent its default behavior. Please remember to remove the event listener after the modal has closed.

Here is the code to make it happen:

// Inside the function that calls dialog.showModal()
const dialog = document.querySelector("dialog");

const openDialog = () => {
  // ...
  dialog.addEventListener("keydown", (e) => {
    if (e.key === "Escape") {
      e.preventDefault();
    }
  });
};

Styles for the dialog element

The user agents provide some default styles for the dialog element. To override these and apply our own styles, we can use this tiny CSS reset.

dialog {
  padding: 0;
  border: none !important;
  /* !important used here to override polyfill CSS, if loaded */
}

Admittedly, there are more default user agent styles, which center the dialog inside the viewport and prevent overflowing content. We’ll leave these default styles untouched, because in our case they are desirable.

CSS ::backdrop pseudo-element

Perhaps the coolest thing about the native dialog element is, that it gives us a nice ::backdrop pseudo-element right out of the box. The serves several purposes for us:

  • Overlay to prevent interaction with the background
  • Easily style the surroundings of the dialog while open

Accessibility aspects of a dialog element

To ensure accessibility of your modal dialog you’ve already got a great deal covered by simply using the native HTML dialog element as a modal – i.e. invoked with dialog.showModal(). Thus, the first interactive element will receive focus, once the dialog opens. Additionally, interaction with other content on the page will be blocked while the dialog is active. Plus, the dialog closes with a keystroke on ESC. Everything coming “for free” along with the native dialog element.

In contrast to using a generic div as a wrapper instead of the semantically correct dialog element, you do not have to put role="dialog" accompanied by aria-modal="true.

Apart from these benefits the native dialog element has to offer, we need to make sure the following aspects are implemented:

  • Put a label on the dialog element – e.g. <dialog aria-label="Use location services?"> or use aria-labelledby if you want to reference the ID of another element inside the dialog’s body, which presents the title anyway
  • If the dialog message requires additional information, which may already be visible in the dialog’s body, you can optionally reference this text with aria-describedby or phrase a description just for screen readers inside an aria-description
  • Return focus to the element, which opened the dialog in the first place, if the dialog has been triggered by a click interaction. This is to ensure that the user can continue browsing the site or application from the same point of regard where they left off before the dialog popped up.

Polyfill for the native dialog element

Sadly, the native HTML dialog element still lacks browser support here and there. As of this writing, Chrome, Edge and Opera support it, Firefox hides support behind a flag. No support from Safari and IE. The support coverage is around 75% globally. Reference browser support

On the bright side, the dialog element is easily polyfilled with this dialog polyfill from GoogleChrome.

In order to load the polyfill only on those browsers not supporting the dialog element, we check if dialog.showModal is not a function.

const dialog = document.querySelector("dialog");

if (typeof dialog.showModal !== "function") {
  // Load polyfill script
  const polyfill = document.createElement("script");
  polyfill.type = "text/javascript";
  polyfill.src = "dist/dialog-polyfill.js"; // example path
  document.body.append(polyfill);

  // Register polyfill on dialog element once the script has loaded
  polyfill.onload = () => {
    dialogPolyfill.registerDialog(dialog);
  };

  // Load polyfill CSS styles
  const polyfillStyles = document.createElement("link");

  polyfillStyles.rel = "stylesheet";
  polyfillStyles.href = "dialog-polyfill.css";
  document.head.append(polyfillStyles);
}

Example of a styling a native dialog element

Here is a CodePen showing off an accessible, polyfilled modal dialog. It implements the requirements listed above regarding accessibility, managing focus and polyfill on-demand. The style is based on Giovanni Piemontese’s Auto Layout Dialogs – Figma UI Kit.

See the Pen
Accessible Material Dialog
by Christian Kozalla (@ckozalla)
on CodePen.0

Apart from CodePen, you can view the source code of the example here on GitHub. A live example of that native dialog is hosted here.

Wrapping up

In this tutorial discussed the structure and purpose of dialogs regarding user-experience, especially for modal dialogs. We’ve compiled a list of requirements for creating user-friendly dialogs. Naturally, we’ve gone in-depth on the native dialog HTML element and the benefits we gain from using it. We’ve extended its functionality by building a focus trap and managing focus around the life-cycle of the native dialog altogether.

We’ve seen how to implement an accessible modal dialog based on the requirements we set before. Our implementation will be polyfilled only when necessary.

Finally, I’ve noticed during my research about the native dialog element, that its reputation in the community has changed alot over the years. It may have been welcomed with an open mind, but today’s opinions are predominantly criticizing the dialog element while simultaneously advising to rather rely on libraries.

Nevertheless, I’m sure the native dialog element has proven to be a suitable basis for implementing modal dialogs in this tutorial. I definitely had some fun!

Thanks for reading, I hope you enjoyed it!

Related

Another tutorial that might be interesting to you is this one by Osvaldas Valutis, where you’ll learn how to style and customize the upload button (file inputs).

The post How to Implement and Style the Dialog Element appeared first on Codrops.

Comparing HTML Preprocessor Features

(This is a sponsored post.)

Of the languages that browsers speak, I’d wager that the very first one that developers decided needed some additional processing was HTML. Every single CMS in the world (aside from intentionally headless-only CMSs) is essentially an elaborate HTML processor: they take content and squoosh it together with HTML templates. There are dozens of other dedicated HTML processing languages that exist today.

The main needs of HTML processing being:

  • Compose complete HTML documents from parts
  • Template the HTML by injecting variable data

There are plenty of other features they can have, and we’ll get to that, but I think those are the biggies.

This research is brought to you by support from Frontend Masters, CSS-Tricks’ official learning partner.

Need front-end development training?

Frontend Masters is the best place to get it. They have courses on all the most important front-end technologies, from React to CSS, from Vue to D3, and beyond with Node.js and Full Stack.

Diagram showing partials and {{ data }} turning into a complete HTML document.

Consider PHP. It’s literally a “Hypertext Preprocessor.” On this very website, I make use of PHP in order to piece together bits of templated HTML to build the pages and complete content you’re looking at now.

<h2>
  <?php the_title(); // Templating! ?>
</h2>

<?php include("metadata.php"); // Partials! ?>

In the above code, I’ve squooshed some content into an HTML template, which calls another PHP file that likely contains more templated HTML. PHP covers the two biggies for HTML processing and is available with cost-friendly hosting — I’d guess that’s a big reason why PHP-powered websites power a huge chunk of the entire internet.

But PHP certainly isn’t the only HTML preprocessor around, and it requires a server to work. There are many others, some designed specifically to run during a build process before the website is ever requested by users.

Let’s go language-by-language and look at whether or not it supports certain features and how. When possible the link of the preprocessor name links to relevant docs.

Does it allow for templating?

Can you mix in data into the final HTML output?

ProcessorExample
Pug
- var title = "On Dogs: Man's Best Friend";
- var author = "enlore";
h1= title
p Written with love by #{author}
ERB
<%= title %>
<%= description %>

<%= @logged_in_user.name %>
Markdown
PHP
<?php echo $post.title; ?>
<?php echo get_post_description($post.id) ?>
Also has HEREDOC syntax.
Slim
tr
td.name = item.name
Haml
<h1><%= post.title %></h1>
<div class="subhead"><%= post.subtitle %></div>
Liquid
Hello {{ user.name }}!
Go html/template
{{ .Title }}
{{ $address }}
Handlebars
{{firstname}} {{lastname}}
Mustache
Hello {{ firstname }}!
Twig
{{ foo.bar }}
Nunjucks
<h1>{{ title }}</h1>
Kit
<!-- $myVar = We finish each other's sandwiches. -->
<p> <!-- $myVar --> </p>
Sergey

Does it do partials/includes?

Can you compose HTML from smaller parts?

ProcessorExample
Pug
include includes/head.pug
ERB
<%= render 'includes/head' %>
Markdown
PHP
<?php include 'head.php'; ?>
<?php include_once 'meta.php'; ?>
Slim⚠️
If you have access to the Ruby code, it looks like it can do it, but you have to write custom helpers.
Haml
.content
=render 'meeting_info'
Liquid{% render head.html %}
{% render meta.liquid %}
Go html/template{{ partial "includes/head.html" . }}
Handlebars⚠️
Only through registering a partial ahead of time.
Mustache{{> next_more}}
Twig{{ include('page.html', sandboxed = true) }}
Nunjucks{% include "missing.html" ignore missing %}
{% import "forms.html" as forms %}
{{ forms.label('Username') }}
Kit<!-- @import "someFile.kit" -->
<!-- @import "file.html" -->
Sergey<sergey-import src="header" />

Does it do local variables with includes?

As in, can you pass data to the include/partial for it to use specifically? For example, in Liquid, you can pass a second parameter of variables for the partial to use. But in PHP or Twig, there is no such ability—they can only access global variables.

ProcessorExample
PHP
ERB<%= render(
partial: "card",
locals: {
title: "Title"
}
) %>
Markdown
Pug⚠️
Pug has mixins that you can put in a separate file and call. Not quite the same concept but can be used similarly.
Slim
Haml.content
= render :partial => 'meeting_info', :locals => { :info => @info }
Liquid{% render "name", my_variable: my_variable, my_other_variable: "oranges" %}
Go html/template{{ partial "header/site-header.html" . }}
(The period at the end is “variable scoping.”)
Handlebars{{> myPartial parameter=favoriteNumber }}
Mustache
Twig
{% include 'template.html' with {'foo': 'bar'} only %}
Nunjucks{% macro field(name, value='', type='text') %}
<div class="field">
<input type="{{ type }}" name="{{ name }}" value="{{ value | escape }}" />
</div>
{% endmacro %}
Kit
Sergey

Does it do loops?

Sometimes you just need 100 <div>s, ya know? Or more likely, you need to loop over an array of data and output HTML for each entry. There are lots of different types of loops, but having at least one is nice and you can generally make it work for whatever you need to loop.

ProcessorExample
PHPfor ($i = 1; $i <= 10; $i++) {
echo $i;
}
ERB<% for i in 0..9 do %>
<%= @users[i].name %>
<% end %>
Markdown
Pugfor (var x = 1; x < 16; x++)
div= x
Slim- for i in (1..15)
div #{i}
Haml(1..16).each do |i|
%div #{i}
Liquid{% for i in (1..5) %}
{% endfor %}
Go html/template{{ range $i, $sequence := (seq 5) }}
{{ $i }}: {{ $sequence }
{{ end }}
Handlebars{{#each myArray}}
<div class="row"></div>
{{/each}}
Mustache{{#myArray}}
{{name}}
{{/myArray}}
Twig{% for i in 0..10 %}
{{ i }}
{% endfor %}
Nunjucks{% set points = [0, 1, 2, 3, 4] %}
{% for x in points %}
Point: {{ x }}
{% endfor %}
Kit
Sergey

Does it have logic?

Mustache is famous for philosophically being “logic-less.” So sometimes it’s desirable to have a templating language that doesn’t mix in any other functionality, forcing you to deal with your business logic in another layer. Sometimes, a little logic is just what you need in a template. And actually, even Mustache has some basic logic.

ProcessorExample
Pug#user
if user.description
h2.green Description
else if authorised
h2.blue Description
ERB<% if show %>
<% endif %>
Markdown
PHP<?php if (value > 10) { ?>
<?php } ?>
Slim- unless items.empty?If you turn on logic less mode:
- article
h1 = title
-! article
p Sorry, article not found
Hamlif data == true
%p true
else
%p false
Liquid{% if user %}
Hello {{ user.name }}!
{% endif %}
Go html/template{{ if isset .Params "title" }}
<h4>{{ index .Params "title" }}</h4>
{{ end }}
Handlebars{{#if author}}
{{firstName}} {{lastName}}
{{/if}}
Mustache
It’s kind of ironic that Mustache calls itself “Logic-less templates”, but they do kinda have logic in the form of “inverted sections.”
{{#repo}}
{{name}}
{{/repo}}
{{^repo}}
No repos :(
{{/repo}}
Twig{% if online == false %}
Our website is in maintenance mode.
{% endif %}
Nunjucks{% if hungry %}
I am hungry
{% elif tired %}
I am tired
{% else %}
I am good!
{% endif %}
Kit
It can output a variable if it exists, which it calls “optionals”:
<dd class='<!-- $myVar? -->'> Page 1 </dd>
Sergey

Does it have filters?

What I mean by filter here is a way to output content, but change it on the way out. For example, escape special characters or capitalize text.

ProcessorExample
Pug⚠️
Pug thinks of filters as ways to use other languages within Pug, and doesn’t ship with any out of the box.
ERB
Whatever Ruby has, like:
"hello James!".upcase #=> "HELLO JAMES!"
Markdown
PHP$str = "Mary Had A Little Lamb";
$str = strtoupper($str);
echo $str; // Prints MARY HAD A LITTLE LAMB
Slim⚠️
Private only?
Haml⚠️
Very specific one for whitespace removal. Mostly for embedding other languages?
Liquid
Lots of them, and you can use multiple.
{{ "adam!" | capitalize | prepend: "Hello " }}
Go html/template⚠️
Has a bunch of functions, many of which are filter-like.
Handlebars⚠️
Triple-brackets do HTML escaping, but otherwise, you’d have to register your own block helpers.
Mustache
Twig{% autoescape "html" %}
{{ var }}
{{ var|raw }} {# var won't be escaped #}
{{ var|escape }} {# var won't be doubled-escaped #}
{% endautoescape %}
Nunjucks{% filter replace("force", "forth") %}
may the force be with you
{% endfilter %}
Kit
Sergey

Does it have math?

Sometimes math is baked right into the language. Some of these languages are built on top of other languages, and thus use that other language to do the math. Like Pug is written in JavaScript, so you can write JavaScript in Pug, which can do math.

ProcessorSupport
PHP<?php echo 1 + 1; ?>
ERB<%= 1 + 1 %>
Markdown
Pug- const x = 1 + 1
p= x
Slim- x = 1 + 1
p= x
Haml%p= 1 + 1
Liquid{{ 1 | plus: 1 }}
Go html/template
{{add 1 2}}
Handlebars
Mustache
Twig{{ 1 + 1 }}
Nunjucks{{ 1 + 1 }}
Kit
Sergey

Does it have slots / blocks?

The concept of a slot is a template that has special areas within it that are filled with content should it be available. It’s conceptually similar to partials, but almost in reverse. Like you could think of a template with partials as the template calling those partials to compose a page, and you almost think of slots like a bit of data calling a template to turn itself into a complete page. Vue is famous for having slots, a concept that made its way to web components.

ProcessorExample
PHP
ERB
Markdown
Pug
You can pull it off with “mixins”
Slim
Haml
Liquid
Go html/template
Handlebars
Mustache
Twig{% block footer %}
© Copyright 2011 by you.
{% endblock %}
Nunjucks{% block item %}
The name of the item is: {{ item.name }}
{% endblock %}
Kit
Sergey<sergey-slot />

Does it have a special HTML syntax?

HTML has <angle> <brackets> and while whitespace matters a little (a space is a space, but 80 spaces is also… a space), it’s not really a whitespace dependant language like Pug or Python. Changing these things up is a language choice. If all the language does is add in extra syntax, but otherwise, you write HTML as normal HTML, I’m considering that not a special syntax. If the language changes how you write normal HTML, that’s special syntax.

ProcessorExample
PHP
ERBIn Ruby, if you want that you generally do Haml.
Markdown
This is pretty much the whole point of Markdown.
# Title
Paragraph with [link](#link).

- List
- List

> Quote
Pug
Slim
Haml
Liquid
Go html/template
Handlebars
Mustache
Twig
Nunjucks
Kit⚠️
HTML comment directives.
Sergey⚠️
Some invented HTML tags.

Wait wait — what about stuff like React and Vue?

I’d agree that those technologies are component-based and used to do templating and often craft complete pages. They also can do many/most of the features listed here. Them, and the many other JavaScript-based-frameworks like them, are also generally capable of running on a server or during a build step and producing HTML, even if it sometimes feels like an afterthought (but not always). They also have other features that can be extremely compelling, like scoped/encapsulated styles, which requires cooperation between the HTML and CSS, which is an enticing feature.

I didn’t include them because they are generally intentionally used to essentially craft the DOM. They are focused on things like data retrieval and manipulation, state management, interactivity, and such. They aren’t really focused on just being an HTML processor. If you’re using a JavaScript framework, you probably don’t need a dedicated HTML processor, although it absolutely can be done. For example, mixing Markdown and JSX or mixing Vue templates and Pug.

I didn’t even put native web components on the list here because they are very JavaScript-focused.

Other considerations

  • SpeedHow fast does it process? Do you care?
  • Language — What was in what is it written in? Is it compatible with the machines you need to support?
  • Server or Build — Does it require a web server running to work? Or can it be run once during a build process? Or both?

Superchart

TemplatingIncludesLocal VariablesLoopsLogicFiltersMathSlotsSpecial Syntax
PHP
ERB⚠️
Markdown
Pug⚠️
Slim⚠️⚠️
Haml⚠️
Liquid
Go html/template⚠️
Handlebars⚠️
Mustache
Twig
Nunjucks
Kit⚠️
Sergey⚠️

The post Comparing HTML Preprocessor Features appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Collective #680




Theatre.js

Theatre.js is a new hackable JavaScript animation library with visual tools. It animates the DOM, WebGL, and any other JavaScript variable.

Check it out









Open-Meteo

Open-Meteo offers free weather forecast APIs for open-source developers and non-commercial use. No API key is required.

Check it out


Mechanic

Mechanic is an open source framework that makes it easy to create custom, web-based design tools that export design assets right in your browser.

Check it out




Glass

Glass is a new subscription-based photo sharing app and community for professional and amateur photographers alike without dark patterns driving engagement.

Check it out



Standards

A new way to design brand guidelines. A product from Standards Manual. Apply for early access.

Check it out


Q1K3

An incredible Quake clone made by Dominic Szablewski for the 2021 js13k competition.

Check it out


The post Collective #680 appeared first on Codrops.

Creating An Interactive Gantt Chart Component With Vanilla JavaScript

If you work with time data in your app, a graphical visualization as a calendar or Gantt chart is often very useful. At first glance, developing your own chart component seems quite complicated. Therefore, in this article, I will develop the foundation for a Gantt chart component whose appearance and functionality you can customize for any use case.

These are the basic features of the Gantt chart that I would like to implement:

  • The user can choose between two views: year/month or month/day.
  • The user can define the planning horizon by selecting a start date and an end date.
  • The chart renders a given list of jobs that can be moved by drag and drop. The changes are reflected in the state of the objects.
  • Below you can see the resulting Gantt chart in both views. In the monthly version, I have included three jobs as an example.

Below you can see the resulting Gantt chart in both views. In the monthly version, I have included three jobs as an example.

Sample Files And Instructions For Running The Code

You can find the full code snippets of this article in the following files:

Since the code contains JavaScript modules, you can only run the example from an HTTP server and not from the local file system. For testing on your local PC, I’d recommend the module live-server, which you can install via npm.

Alternatively, you can try out the example here directly in your browser without installation.

Basic Structure Of The Web Component

I decided to implement the Gantt chart as a web component. This allows us to create a custom HTML element, in my case <gantt-chart></gantt-chart>, which we can easily reuse anywhere on any HTML page.

You can find some basic information about developing web components in the MDN Web Docs. The following listing shows the structure of the component. It is inspired by the “counter” example from Alligator.io.

The component defines a template containing the HTML code needed to display the Gantt chart. For the complete CSS specifications, please refer to the sample files. The specific selection fields for year, month or date cannot be defined here yet, as they depend on the selected level of the view.

The selection elements are projected in by one of the two renderer classes instead. The same applies to the rendering of the actual Gantt chart into the element with the ID gantt-container, which is also handled by the responsible renderer class.

The class VanillaGanttChart now describes the behavior of our new HTML element. In the constructor, we first define our rough template as the shadow DOM of the element.

The component must be initialized with two arrays, jobs, and resources. The jobs array contains the tasks that are displayed in the chart as movable green bars. The resources array defines the individual rows in the chart where tasks can be assigned. In the screenshots above, for example, we have 4 resources labeled Task 1 to Task 4. The resources can therefore represent the individual tasks, but also people, vehicles, and other physical resources, allowing for a variety of use cases.

Currently, the YearMonthRenderer is used as the default renderer. As soon as the user selects a different level, the renderer is changed in the changeLevel method: First, the renderer-specific DOM elements and listeners are deleted from the Shadow DOM using the clear method of the old renderer. Then the new renderer is initialized with the existing jobs and resources and the rendering is started.

import {YearMonthRenderer} from './YearMonthRenderer.js';
import {DateTimeRenderer} from './DateTimeRenderer.js';

const template = document.createElement('template');

template.innerHTML = 
 `<style> … </style>

  <div id="gantt-settings">

    <select name="select-level" id="select-level">
      <option value="year-month">Month / Day</option>
      <option value="day">Day / Time</option>
    </select>

    <fieldset id="select-from">
      <legend>From</legend>
    </fieldset>

    <fieldset id="select-to">
      <legend>To</legend>
    </fieldset>
  </div>

  <div id="gantt-container">
  </div>`;

export default class VanillaGanttChart extends HTMLElement {

    constructor() {
      super();
      this.attachShadow({ mode: 'open' });
      this.shadowRoot.appendChild(template.content.cloneNode(true));
      this.levelSelect = this.shadowRoot.querySelector('#select-level');
    }

    _resources = [];
    _jobs = [];
    _renderer;

    set resources(list){…}
    get resources(){…}
    set jobs(list){…}
    get jobs(){…}
    get level() {…}
    set level(newValue) {…} 
    get renderer(){…}
    set renderer(r){…}

    connectedCallback() {
      this.changeLevel = this.changeLevel.bind(this);

      this.levelSelect.addEventListener('change', this.changeLevel);
      this.level = "year-month";   

      this.renderer = new YearMonthRenderer(this.shadowRoot);
      this.renderer.dateFrom = new Date(2021,5,1);
      this.renderer.dateTo = new Date(2021,5,24);
      this.renderer.render();
    }

    disconnectedCallback() {  
      if(this.levelSelect)
        this.levelSelect.removeEventListener('change', this.changeLevel);
      if(this.renderer)
        this.renderer.clear();
    }

    changeLevel(){
      if(this.renderer)
        this.renderer.clear();

      var r;   

      if(this.level == "year-month"){
        r = new YearMonthRenderer(this.shadowRoot);    
      }else{
        r = new DateTimeRenderer(this.shadowRoot);
      }

      r.dateFrom = new Date(2021,5,1);
      r.dateTo = new Date(2021,5,24);
      r.resources = this.resources;
      r.jobs = this.jobs;
      r.render();
      this.renderer = r;
    }
  }

  window.customElements.define('gantt-chart', VanillaGanttChart);

Before we get deeper into the rendering process, I would like to give you an overview of the connections between the different scripts:

  • index.html is your web page where you can use the tag <gantt-chart></gantt-chart>
  • index.js is a script in which you initialize the instance of the web component that is associated with the Gantt chart used in index.html with the appropriate jobs and resources (of course you can also use multiple Gantt charts and thus multiple instances of the web component)
  • The component VanillaGanttChart delegates rendering to the two renderer classes YearMonthRenderer and DateTimeRenderer.

Rendering Of The Gantt chart With JavaScript And CSS Grid

In the following, we discuss the rendering process using the YearMonthRenderer as an example. Please note that I have used a so-called constructor function instead of the class keyword to define the class. This allows me to distinguish between public properties (this.render and this.clear) and private variables (defined with var).

The rendering of the chart is broken down into several sub-steps:

  1. initSettings
    Rendering of the controls which are used to define the planning horizon.
  2. initGantt
    Rendering of the Gantt chart, basically in four steps:
    • initFirstRow (draws 1 row with month names)
    • initSecondRow (draws 1 row with days of the month)
    • initGanttRows (draws 1 row for each resource with grid cells for each day of the month)
    • initJobs (positions the draggable jobs in the chart)
export function YearMonthRenderer(root){

    var shadowRoot = root;
    var names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];    

    this.resources=[];
    this.jobs = [];

    this.dateFrom = new Date();
    this.dateTo = new Date();

    //select elements
    var monthSelectFrom;
    var yearSelectFrom;
    var monthSelectTo;
    var yearSelectTo;

    var getYearFrom = function() {…}
    var setYearFrom = function(newValue) {…}

    var getYearTo = function() {…}
    var setYearTo = function(newValue) {…}

    var getMonthFrom = function() {…}
    var setMonthFrom = function(newValue) {…}

    var getMonthTo = function() {…}
    var setMonthTo = function(newValue) {…}  

    this.render = function(){
      this.clear();
      initSettings();
      initGantt();
    }

    //remove select elements and listeners, clear gantt-container 
    this.clear = function(){…}

    //add HTML code for the settings area (select elements) to the shadow root, initialize associated DOM elements and assign them to the properties monthSelectFrom, monthSelectTo etc., initialize listeners for the select elements
    var initSettings = function(){…}

    //add HTML code for the gantt chart area to the shadow root, position draggable jobs in the chart
    var initGantt = function(){…}

    //used by initGantt: draw time axis of the chart, month names
    var initFirstRow = function(){…}

    //used by initGantt: draw time axis of the chart, days of month
    var initSecondRow = function(){…}

    //used by initGantt: draw the remaining grid of the chart
    var initGanttRows = function(){…}.bind(this);

    //used by initGantt: position draggable jobs in the chart cells
    var initJobs = function(){…}.bind(this);    

   //drop event listener for jobs
   var onJobDrop = function(ev){…}.bind(this);

   //helper functions, see example files
   ...
}

Rendering The Grid

I recommend CSS Grid for drawing the diagram area because it makes it very easy to create multi-column layouts that adapt dynamically to the screen size.

In the first step, we have to determine the number of columns of the grid. In doing so, we refer to the first row of the chart which (in the case of the YearMonthRenderer) represents the individual months.

Consequently, we need:

  • one column for the names of the resources, e.g. with a fixed width of 100px.
  • one column for each month, of the same size and using the full space available.

This can be achieved with the setting 100px repeat(${n_months}, 1fr) for the property gridTemplateColumns of the chart container.

This is the initial part of the initGantt method:

var container = shadowRoot.querySelector("#gantt-container");
container.innerHTML = "";

var first_month = new Date(getYearFrom(), getMonthFrom(), 1);
var last_month = new Date(getYearTo(), getMonthTo(), 1);

//monthDiff is defined as a helper function at the end of the file
var n_months =  monthDiff(first_month, last_month)+1;

container.style.gridTemplateColumns = `100px repeat(${n_months},1fr)`;

In the following picture you can see a chart for two months with n_months=2:

After we have defined the outer columns, we can start filling the grid. Let's stay with the example from the picture above. In the first row, I insert 3 divs with the classes gantt-row-resource and gantt-row-period. You can find them in the following snippet from the DOM inspector.

In the second row, I use the same three divs to keep the vertical alignment. However, the month divs get child elements for the individual days of the month.

<div id="gantt-container"
  style="grid-template-columns: 100px repeat(2, 1fr);">
  <div class="gantt-row-resource"></div>
  <div class="gantt-row-period">Jun 2021</div>
  <div class="gantt-row-period">Jul 2021</div>
  <div class="gantt-row-resource"></div>
  <div class="gantt-row-period">
    <div class="gantt-row-period">1</div>
    <div class="gantt-row-period">2</div>
    <div class="gantt-row-period">3</div>
    <div class="gantt-row-period">4</div>
    <div class="gantt-row-period">5</div>
    <div class="gantt-row-period">6</div>
    <div class="gantt-row-period">7</div>
    <div class="gantt-row-period">8</div>
    <div class="gantt-row-period">9</div>
    <div class="gantt-row-period">10</div>
  ...
  </div>
  ...
</div>

For the child elements to be arranged horizontally as well, we need the setting display: grid for the class gantt-row-period. In addition, we do not know exactly how many columns are required for the individual months (28, 30, or 31). Therefore, I use the setting grid-auto-columns. With the value minmax(20px, 1fr); I can ensure that a minimum width of 20px is maintained and that otherwise the available space is fully utilized:

#gantt-container {
  display: grid;
}

.gantt-row-resource {
  background-color: whitesmoke;
  color: rgba(0, 0, 0, 0.726);
  border: 1px solid rgb(133, 129, 129);
  text-align: center;
}

.gantt-row-period {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(20px, 1fr);
  background-color: whitesmoke;
  color: rgba(0, 0, 0, 0.726);
  border: 1px solid rgb(133, 129, 129);
  text-align: center;
}

The remaining rows are generated according to the second row, however as empty cells.

Here is the JavaScript code for generating the individual grid cells of the first row. The methods initSecondRow and initGanttRows have a similar structure.

var initFirstRow = function(){

  if(checkElements()){
        var container = shadowRoot.querySelector("#gantt-container");

        var first_month = new Date(getYearFrom(), getMonthFrom(), 1);
        var last_month = new Date(getYearTo(), getMonthTo(), 1);

        var resource = document.createElement("div");
        resource.className = "gantt-row-resource";
        container.appendChild(resource);   

        var month = new Date(first_month);

        for(month; month <= last_month; month.setMonth(month.getMonth()+1)){    
          var period = document.createElement("div");
          period.className = "gantt-row-period";
          period.innerHTML = names[month.getMonth()] + " " + month.getFullYear();
          container.appendChild(period);
        }
  }
}

Rendering The Jobs

Now each job has to be drawn into the diagram at the correct position. For this I make use of the HTML data attributes: every grid cell in the main chart area is associated with the two attributes data-resource and data-date indicating the position on the horizontal and vertical axis of the chart (see function initGanttRows in the files YearMonthRenderer.js and DateTimeRenderer.js).

As an example, let's look at the first four grid cells in the first row of the chart (we are still using the same example as in the pictures above):

In the DOM inspector you can see the values of the data attributes that I have assigned to the individual cells:

Let's now see what this means for the function initJobs. With the help of the function querySelector, it is now quite easy to find the grid cell into which a job should be placed.

The next challenge is to determine the correct width for a job element. Depending on the selected view, each grid cell represents a unit of one day (level month/day) or one hour (level day/time). Since each job is the child element of a cell, the job duration of 1 unit (day or hour) corresponds to a width of 1*100%, the duration of 2 units corresponds to a width of 2*100%, and so on. This makes it possible to use the CSS calc function to dynamically set the width of a job element, as shown in the following listing.

var initJobs = function(){

    this.jobs.forEach(job => {

        var date_string = formatDate(job.start);

        var ganttElement = shadowRoot.querySelector(`div[data-resource="${job.resource}"][data-date="${date_string}"]`);

        if(ganttElement){

          var jobElement = document.createElement("div");
          jobElement.className="job";
          jobElement.id = job.id;

          //helper function dayDiff - get difference between start and end in days
          var d = dayDiff(job.start, job.end);           

          //d --> number of grid cells covered by job + sum of borderWidths
          jobElement.style.width = "calc("+(d*100)+"% + "+ d+"px)";
          jobElement.draggable = "true";

          jobElement.ondragstart = function(ev){
              //the id is used to identify the job when it is dropped
              ev.dataTransfer.setData("job", ev.target.id); 
          };

          ganttElement.appendChild(jobElement);
        }
    });
  }.bind(this);

In order to make a job draggable, there are three steps required:

  • Set the property draggable of the job element to true (see listing above).
  • Define an event handler for the event ondragstart of the job element (see listing above).
  • Define an event handler for the event ondrop for the grid cells of the Gantt chart, which are the possible drop targets of the job element (see function initGanttRows in the file YearMonthRenderer.js).

The event handler for the event ondrop is defined as follows:

var onJobDrop = function(ev){

      // basic null checks
      if (checkElements()) {

        ev.preventDefault(); 

        // drop target = grid cell, where the job is about to be dropped
        var gantt_item = ev.target;

        // prevent that a job is appended to another job and not to a grid cell
        if (ev.target.classList.contains("job")) {
          gantt_item = ev.target.parentNode;
        }

        // identify the dragged job
        var data = ev.dataTransfer.getData("job");               
        var jobElement = shadowRoot.getElementById(data);  

        // drop the job
        gantt_item.appendChild(jobElement);

        // update the properties of the job object
        var job = this.jobs.find(j => j.id == data );

        var start = new Date(gantt_item.getAttribute("data-date"));
        var end = new Date(start);
        end.setDate(start.getDate()+dayDiff(job.start, job.end));

        job.start = start;
        job.end = end;
        job.resource = gantt_item.getAttribute("data-resource");
      }
    }.bind(this);

All changes to the job data made by drag and drop are thus reflected in the list jobs of the Gantt chart component.

Integrating The Gantt Chart Component In Your Application

You can use the tag <gantt-chart></gantt-chart> anywhere in the HTML files of your application (in my case in the file index.html) under the following conditions:

  • The script VanillaGanttChart.js must be integrated as a module so that the tag is interpreted correctly.
  • You need a separate script in which the Gantt chart is initialized with jobs and resources (in my case the file index.js).
<!DOCTYPE html>
<html>
 <head>
   <meta charset="UTF-8"/>
   <title>Gantt chart - Vanilla JS</title>
   <script type="module" src="VanillaGanttChart.js"></script>   
 </head>

 <body>

  <gantt-chart id="g1"></gantt-chart> 

  <script type="module" src="index.js"></script>
 </body> 
</html>

For example, in my case the file index.js looks as follows:

import VanillaGanttChart from "./VanillaGanttChart.js";

var chart = document.querySelector("#g1");

chart.jobs = [
    {id: "j1", start: new Date("2021/6/1"), end: new Date("2021/6/4"), resource: 1},
    {id: "j2", start: new Date("2021/6/4"), end: new Date("2021/6/13"), resource: 2},
    {id: "j3", start: new Date("2021/6/13"), end: new Date("2021/6/21"), resource: 3},
];

chart.resources = [{id:1, name: "Task 1"}, {id:2, name: "Task 2"}, {id:3, name: "Task 3"}, {id:4, name: "Task 4"}];

However, there is still one requirement open: when the user makes changes by dragging jobs in the Gantt chart, the respective changes in the property values of the jobs should be reflected in the list outside the component.

We can achieve this with the use of JavaScript Proxy Objects: Each job is nested in a proxy object, which we provide with a so-called validator. It becomes active as soon as a property of the object is changed (function set of the validator) or retrieved (function get of the validator). In the set function of the validator, we can store code that is executed whenever the start time or the resource of a task is changed.

The following listing shows a different version of the file index.js. Now a list of proxy objects is assigned to the Gantt chart component instead of the original jobs. In the validator set I use a simple console output to show that I have been notified of a property change.

import VanillaGanttChart from "./VanillaGanttChart.js";

var chart = document.querySelector("#g1");

var jobs = [
    {id: "j1", start: new Date("2021/6/1"), end: new Date("2021/6/4"), resource: 1},
    {id: "j2", start: new Date("2021/6/4"), end: new Date("2021/6/13"), resource: 2},
    {id: "j3", start: new Date("2021/6/13"), end: new Date("2021/6/21"), resource: 3},
];
var p_jobs = [];

chart.resources = [{id:1, name: "Task 1"}, {id:2, name: "Task 2"}, {id:3, name: "Task 3"}, {id:4, name: "Task 4"}];

jobs.forEach(job => {

    var validator = {
        set: function(obj, prop, value) {

          console.log("Job " + obj.id + ": " + prop + " was changed to " + value);
          console.log();

          obj[prop] = value;
          return true;
        },

        get: function(obj, prop){

            return obj[prop];
        }
    };

    var p_job = new Proxy(job, validator);
    p_jobs.push(p_job);
});

chart.jobs = p_jobs;
Outlook

The Gantt chart is an example that shows how you can use the technologies of Web Components, CSS Grid, and JavaScript Proxy to develop a custom HTML element with a somewhat more complex graphical interface. You are welcome to develop the project further and/or use it in your own projects together with other JavaScript frameworks.

Again, you can find all sample files and instructions at the top of the article.

6 Resources For JavaScript Code Snippets

When it comes to writing JavaScript (or any other code, for that matter) you can save a lot of time by not trying to reinvent the wheel – or coding something that is common enough that it has already been written countless times. In these instances it is helpful to have a list of collections of commonly (or sometimes not so commonly) used scripts or snippets you can refer to or search through to find what you need to either get your code started or solve your whole problem.

That is why we’ve put together this list of collections of JavaScript snippets so you can bookmark and refer back to them whenever you are in need. Here are six useful resources for Javascript code snippets.

The UX Designer Toolbox

Unlimited Downloads: 500,000+ Wireframe & UX Templates, UI Kits & Design Assets
Starting at only $16.50 per month!


 

30 seconds of code

This JavaScript snippet collection contains a wide variety of ES6 helper functions. It includes helpers for dealing with primitives, arrays and objects, as well as algorithms, DOM manipulation functions and Node.js utilities.

30 seconds of code - JavaScript snippets

JavaScriptTutorial.net

This website has a section that provides you with handy functions for selecting, traversing, and manipulating DOM elements.

JavaScript Tutorial snippets

HTMLDOM.dev

This website focuses on scripts that manage HTML DOM with vanilla JavaScript, providing a nice collection of scripts that do just that.

HTMLDOM.dev

The Vanilla JavaScript Toolkit

Here’s a collection of native JavaScript methods, helper functions, libraries, boilerplates, and learning resources.

Vanilla JavaScript Toolkit

CSS Tricks

CSS Tricks has a nice collection of all different kinds of code snippets, including this great list of JS snippets.

CSS Tricks snippets

Top 100 JavaScript Snippets for Beginners

Focusing on beginners, and a bit dated, but this list is still a worthwhile resource to keep in your back pocket.

Topp 100 Scripts for beginners

We hope you find this list helpful in your future projects. Be sure to check out all of the other JavaScript resources we have here at 1stWebDesigner while you’re at it!