Tag: Library

Introducing Shoelace, a Framework-Independent Component-Based UX Library

This is a post about Shoelace, a component library by Cory LaViska, but with a twist. It defines all your standard UX components: tabs, modals, accordions, auto-completes, and much, much more. They look beautiful out of the box, are accessible, and fully customizable. But rather than creating these components in React, or Solid, or Svelte, etc., it creates them with Web Components; this means you can use them with any framework.

Some preliminary things

Web Components are great, but there’s currently a few small hitches to be aware of.

React

I said they work in any JavaScript framework, but as I’ve written before, React’s support for Web Components is currently poor. To address this, Shoelace actually created wrappers just for React.

Another option, which I personally like, is to create a thin React component that accepts the tag name of a Web Component and all of its attributes and properties, then does the dirty work of handling React’s shortcomings. I talked about this option in a previous post. I like this solution because it’s designed to be deleted. The Web Component interoperability problem is currently fixed in React’s experimental branch, so once that’s shipped, any thin Web Component-interoperable component you’re using could be searched, and removed, leaving you with direct Web Component usages, without any React wrappers.

Server-Side Rendering (SSR)

Support for SSR is also poor at the time of this writing. In theory, there’s something called Declarative Shadow DOM (DSD) which would enable SSR. But browser support is minimal, and in any event, DSD actually requires server support to work right, which means Next, Remix, or whatever you happen to use on the server will need to become capable of some special handling.

That said, there are other ways to get Web Components to just work with a web app that’s SSR’d with something like Next. The short version is that the scripts registering your Web Components need to run in a blocking script before your markup is parsed. But that’s a topic for another post.

Of course, if you’re building any kind of client-rendered SPA, this is a non-issue. This is what we’ll work with in this post.

Let’s start

Since I want this post to focus on Shoelace and on its Web Component nature, I’ll be using Svelte for everything. I’ll also be using this Stackblitz project for demonstration. We’ll build this demo together, step-by-step, but feel free to open that REPL up anytime to see the end result.

I’ll show you how to use Shoelace, and more importantly, how to customize it. We’ll talk about Shadow DOMs and which styles they block from the outside world (as well as which ones they don’t). We’ll also talk about the ::part CSS selector — which may be entirely new to you — and we’ll even see how Shoelace allows us to override and customize its various animations.

If you find you like Shoelace after reading this post and want to try it in a React project, my advice is to use a wrapper like I mentioned in the introduction. This will allow you to use any of Shoelace’s components, and it can be removed altogether once React ships the Web Component fixes they already have (look for that in version 19).

Introducing Shoelace

Shoelace has fairly detailed installation instructions. At its most simple, you can dump <script> and <style> tags into your HTML doc, and that’s that. For any production app, though, you’ll probably want to selectively import only what you want, and there are instructions for that, too.

With Shoelace installed, let’s create a Svelte component to render some content, and then go through the steps to fully customize it. To pick something fairly non-trivial, I went with the tabs and a dialog (commonly referred to as a modal) components. Here’s some markup taken largely from the docs:

<sl-tab-group>   <sl-tab slot="nav" panel="general">General</sl-tab>   <sl-tab slot="nav" panel="custom">Custom</sl-tab>   <sl-tab slot="nav" panel="advanced">Advanced</sl-tab>   <sl-tab slot="nav" panel="disabled" disabled>Disabled</sl-tab>    <sl-tab-panel name="general">This is the general tab panel.</sl-tab-panel>   <sl-tab-panel name="custom">This is the custom tab panel.</sl-tab-panel>   <sl-tab-panel name="advanced">This is the advanced tab panel.</sl-tab-panel>   <sl-tab-panel name="disabled">This is a disabled tab panel.</sl-tab-panel> </sl-tab-group>  <sl-dialog no-header label="Dialog">   Hello World!   <button slot="footer" variant="primary">Close</button> </sl-dialog>  <br /> <button>Open Dialog</button>

This renders some nice, styled tabs. The underline on the active tab even animates nicely, and slides from one active tab to the next.

Four horizontal tab headings with the first active in blue with placeholder content contained in a panel below.
Default tabs in Shoelace

I won’t waste your time running through every inch of the APIs that are already well-documented on the Shoelace website. Instead, let’s look into how best to interact with, and fully customize these Web Components.

Interacting with the API: methods and events

Calling methods and subscribing to events on a Web Component might be slightly different than what you’re used to with your normal framework of choice, but it’s not too complicated. Let’s see how.

Tabs

The tabs component (<sl-tab-group>) has a show method, which manually shows a particular tab. In order to call this, we need to get access to the underlying DOM element of our tabs. In Svelte, that means using bind:this. In React, it’d be a ref. And so on. Since we’re using Svelte, let’s declare a variable for our tabs instance:

<script>   let tabs; </script>

…and bind it:

<sl-tab-group bind:this="{tabs}"></sl-tab-group>

Now we can add a button to call it:

<button on:click={() => tabs.show("custom")}>Show custom</button>

It’s the same idea for events. There’s a sl-tab-show event that fires when a new tab is shown. We could use addEventListener on our tabs variable, or we can use Svelte’s on:event-name shortcut.

<sl-tab-group bind:this={tabs} on:sl-tab-show={e => console.log(e)}>

That works and logs the event objects as you show different tabs.

Event object meta shown in DevTools.

Typically we render tabs and let the user click between them, so this work isn’t usually even necessary, but it’s there if you need it. Now let’s get the dialog component interactive.

Dialog

The dialog component (<sl-dialog>) takes an open prop which controls whether the dialog is… open. Let’s declare it in our Svelte component:

<script>   let tabs;   let open = false; </script>

It also has an sl-hide event for when the dialog is hidden. Let’s pass our open prop and bind to the hide event so we can reset it when the user clicks outside of the dialog content to close it. And let’s add a click handler to that close button to set our open prop to false, which would also close the dialog.

<sl-dialog no-header {open} label="Dialog" on:sl-hide={() => open = false}>   Hello World!   <button slot="footer" variant="primary" on:click={() => open = false}>Close</button> </sl-dialog>

Lastly, let’s wire up our open dialog button:

<button on:click={() => (open = true)}>Open Dialog</button>

And that’s that. Interacting with a component library’s API is more or less straightforward. If that’s all this post did, it would be pretty boring.

But Shoelace — being built with Web Components — means that some things, particularly styles, will work a bit differently than we might be used to.

Customize all the styles!

As of this writing, Shoelace is still in beta and the creator is considering changing some default styles, possibly even removing some defaults altogether so they’ll no longer override your host application’s styles. The concepts we’ll cover are relevant either way, but don’t be surprised if some of the Shoelace specifics I mention are different when you go to use it.

As nice as Shoelace’s default styles are, we might have our own designs in our web app, and we’ll want our UX components to match. Let’s see how we’d go about that in a Web Components world.

We won’t try to actually improve anything. The Shoelace creator is a far better designer than I’ll ever be. Instead, we’ll just look at how to change things, so you can adapt to your own web apps.

A quick tour of Shadow DOMs

Take a peek at one of those tab headers in your DevTools; it should look something like this:

The tabs component markup shown in DevTools.

Our tab element has created a div container with a .tab and .tab--active class, and a tabindex, while also displaying the text we entered for that tab. But notice that it’s sitting inside of a shadow root. This allows Web Component authors to add their own markup to the Web Component while also providing a place for the content we provide. Notice the <slot> element? That basically means “put whatever content the user rendered between the Web Component tags here.”

So the <sl-tab> component creates a shadow root, adds some content to it to render the nicely-styled tab header along with a placeholder (<slot>) that renders our content inside.

Encapsulated styles

One of the classic, more frustrating problems in web development has always been styles cascading to places where we don’t want them. You might worry that any style rules in our application which specify something like div.tab would interfere with these tabs. It turns out this isn’t a problem; shadow roots encapsulate styles. Styles from outside the shadow root do not affect what’s inside the shadow root (with some exceptions which we’ll talk about), and vice versa.

The exceptions to this are inheritable styles. You, of course, don’t need to apply a font-family style for every element in your web app. Instead, you can specify your font-family once, on :root or html and have it inherit everywhere beneath it. This inheritance will, in fact, pierce the shadow root as well.

CSS custom properties (often called “css variables”) are a related exception. A shadow root can absolutely read a CSS property that is defined outside the shadow root; this will become relevant in a moment.

The ::part selector

What about styles that don’t inherit. What if we want to customize something like cursor, which doesn’t inherit, on something inside of the shadow root. Are we out of luck? It turns out we’re not. Take another look at the tab element image above and its shadow root. Notice the part attribute on the div? That allows you to target and style that element from outside the shadow root using the ::part selector. We’ll walk through an example is a bit.

Overriding Shoelace styles

Let’s see each of these approaches in action. As of now, a lot of Shoelace styles, including fonts, receive default values from CSS custom properties. To align those fonts with your application’s styles, override the custom props in question. See the docs for info on which CSS variables Shoelace is using, or you can simply inspect the styles in any given element in DevTools.

Inheriting styles through the shadow root

Open the app.css file in the src directory of the StackBlitz project. In the :root section at the bottom, you should see a letter-spacing: normal; declaration. Since the letter-spacing property is inheritable, try setting a new value, like 2px. On save, all content, including the tab headers defined in the shadow root, will adjust accordingly.

Four horizontal tab headers with the first active in blue with plqceholder content contained in a panel below. The text is slightly stretched with letter spacing.

Overwriting Shoelace CSS variables

The <sl-tab-group> component reads an --indicator-color CSS custom property for the active tab’s underline. We can override this with some basic CSS:

sl-tab-group {   --indicator-color: green; }

And just like that, we now have a green indicator!

Four horizontal tab headers with the first active with blue text and a green underline.

Querying parts

In the version of Shoelace I’m using right now (2.0.0-beta.83), any non-disabled tab has a pointer cursor. Let’s change that to a default cursor for the active (selected) tab. We already saw that the <sl-tab> element adds a part="base" attribute on the container for the tab header. Also, the currently selected tab receives an active attribute. Let’s use these facts to target the active tab, and change the cursor:

sl-tab[active]::part(base) {   cursor: default; }

And that’s that!

Customizing animations

For some icing on the metaphorical cake, let’s see how Shoelace allows us to customize animations. Shoelace uses the Web Animations API, and exposes a setDefaultAnimation API to control how different elements animate their various interactions. See the docs for specifics, but as an example, here’s how you might change Shoelace’s default dialog animation from expanding outward, and shrinking inward, to instead animate in from the top, and drop down while hiding.

import { setDefaultAnimation } from "@shoelace-style/shoelace/dist/utilities/animation-registry";  setDefaultAnimation("dialog.show", {   keyframes: [     { opacity: 0, transform: "translate3d(0px, -20px, 0px)" },     { opacity: 1, transform: "translate3d(0px, 0px, 0px)" },   ],   options: { duration: 250, easing: "cubic-bezier(0.785, 0.135, 0.150, 0.860)" }, }); setDefaultAnimation("dialog.hide", {   keyframes: [     { opacity: 1, transform: "translate3d(0px, 0px, 0px)" },     { opacity: 0, transform: "translate3d(0px, 20px, 0px)" },   ],   options: { duration: 200, easing: "cubic-bezier(0.785, 0.135, 0.150, 0.860)" }, });

That code is in the App.svelte file. Comment it out to see the original, default animation.

Wrapping up

Shoelace is an incredibly ambitious component library that’s built with Ceb Components. Since Web Components are framework-independent, they can be used in any project, with any framework. With new frameworks starting to come out with both amazing performance characteristics, and also ease of use, the ability to use quality user experience widgets which aren’t tied to any one framework has never been more compelling.


Introducing Shoelace, a Framework-Independent Component-Based UX Library originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , ,

How I Chose an Animation Library for My Solitaire Game

There is an abundance of both CSS and JavaScript libraries for animation libraries out there. So many, in fact, that choosing the right one for your project can seem impossible. That’s the situation I faced when I decided to build an online Solitaire game. I knew I’d need an animation library, but which was the right one to choose?

In this article, I’ll go through which considerations I made, what to look out for and present you with some of the most popular libraries available. I’ll go through some real-world examples with you to illustrate my points, and in the end, hopefully, you’ll be better equipped than me when I first had to choose an animation library.

Your mileage with this advice may vary, of course. Everything I’m sharing here is specific to a thing I wanted to build. Your project may have completely different requirements and priorities and that’s OK. I think what’s important here is getting a first-hand account of thinking like a front-end developer with a particular goal.

Speaking of which, I do consider myself a front-end developer but my background is super heavy in design. So I know code, but not to the extent of someone who is a JavaScript engineer. Just wanted to clear that up because experience can certainly impact the final decision.

Here’s the goal

Before we get into any decision-making let’s take a look at the sorts of animations I needed to make in this CSS-Tricks-ified version of the game:

Crazy, right? There’s nothing exactly trivial about these animations. There’s a lot going on — sometimes simultaneously — and a lot to orchestrate. Plus, a majority of the animations are triggered by user interactions. So, that left me with a few priorities heading into my decision:

  • Smooth animations: The way animations are applied can have a big impact on whether they run smoothly, or display a little choppiness.
  • Performance: Adopting any library is going to add weight to a project and I wanted my game to be as lean as possible.
  • Convenience: I wanted a nice, clean syntax that makes it easier to write and manage the animations. I’d even trade a little extra convenience for a small performance cost if it allows me to write better, more maintainable code. Again, this bodes well for a designer-turned-developer.
  • Browser support: Of course I wanted my game to work on any modern browser using some form of progressive enhancement to prevent completely borking legacy browsers. Plus, I definitely wanted  some future-proofing.

That’s what I took with me as I went in search of the right tool for this particular job.

Choosing between CSS or JavaScript animation libraries

The first thing I considered when choosing an animation library was whether to go with a CSS or JavaScript-based library. There are lots of great CSS libraries, many of them with excellent performance which was a high priority for me. I was looking to do some heavy-duty animations, like the  ability to sequence animations and get callbacks on animation completion. That’s all totally possible with pure CSS — still, it’s a lot less smooth than what most JavaScript libraries offer.

Let’s see how a simple sequenced animation looks in CSS and compare it to jQuery, which has plenty of built-in animation helpers:

The animations look the same but are created differently. To make the CSS animation, first, we have to define the keyframe animation in our CSS and attach it to a class:

.card.move {   animation : move 2s; }  @keyframes move {   0% { left: 0 }   50% { left: 100px }   100% { left: 0 } }

We then execute the animation using JavaScript and listen for a CSS callback on the element:

var cardElement = document.getElementsByClassName("card")[0]; var statusElement = document.getElementsByClassName("status")[0];  cardElement.classList.add("move"); statusElement.innerHTML = "Animating"  var animationEndCallback = function() {   cardElement.classList.remove("move");   statusElement.innerHTML = "Inactive" }  cardElement.addEventListener("webkitAnimationEnd", animationEndCallback); cardElement.addEventListener("oAnimationEnd", animationEndCallback);  cardElement.addEventListener("antionend", animationEndCallback);

Having things happen in different places might be fine in a simple example like this, but it can become very confusing once things get a bit more complex. 

Compare this to how the animation is done with jQuery:

$  (".status").text("Animating") $  ( ".card" ).animate({   left: "100px" }, 1000); $  ( ".card" ).animate({   left: 0 }, 1000, function() {   $  (".status").text("Inactive") });

Here, everything happens in the same place, simplifying things should the animations grow more complex in the future.

It seemed clear that a JavaScript library was the right way to go, but which was the right one to choose for my Solitaire game? I mean, jQuery is great and still widely used even today, but that’s not something I want to hang my hat on. There are plenty of JavaScript animation libraries, so I wanted to consider something built specifically to handle the type of heavy animations I had in mind.

Choosing a JavaScript animation library

It quickly became apparent to me that there’s no lack of JavaScript animation libraries and new, exciting technologies. They all have benefits and drawbacks, so let’s go through some of the ones I considered and why.

The Web Animations API is one such case that might replace many JavaScript animation libraries in the future. With it, you’ll be able to create complex staggered animations without loading any external libraries and with the same performance as CSS animations. The only drawback is that not all browsers support it yet

The <canvas> element presents another exciting opportunity. In it, we can animate things with JavaScript, as we would with the DOM, but the animation is rendered as raster, which means we can make some high-performance animations. The only drawback is that the canvas element is essentially rendered as an image in the DOM, so if we’re looking for pixel-perfection, we might be out of luck. As someone acutely in tune with design, this was a dealbreaker for me.

I needed something tried and tested, so I knew I probably had to go with one of the many JavaScript libraries. I started looking at libraries and narrowed my choices to Anime.js and GSAP. They both seemed to handle complex animations well and had excellent notes on performance. Anime is a well-maintained library with over 42.000 stars on GitHub, while GSAP is a super popular, battle-tested library with a thriving community.

An active community was critical to me since I needed a place to ask for help, and I didn’t want to use a library that might later be abandoned. I considered this as part of my convenience requirements.

Sequencing animations and callbacks

Once I had my choices narrowed down, the next step was to implement a complex animation using my two libraries. A recurrent animation in a solitaire game is that of a card moving somewhere and then turning over, so let’s see how that looks:

Both animations look great! They’re smooth, and implementing both of them was pretty straightforward. Both libraries had a timeline function that made creating sequences a breeze. This is how the implementation looks in AnimeJS:

var timeline = anime.timeline({   begin: function() {     $  (".status").text("Animating")   },   complete: function() {     $  (".status").text("Inactive")   } });  timeline.add({   targets: '.card',   left: [0, 300],   easing: 'easeInOutSine',   duration: 500 }).add({   targets: '.card .back',   rotateY: [0, 90],   easing: 'easeInSine',   duration: 200 }).add({   targets: '.card .front',   rotateY: [-90, 0],   easing: 'easeOutSine',   duration: 200 })

Anime’s timeline() function comes built-in with callbacks on beginning and ending the animation, and creating the sequence is as easy as appending the sequential animations. First, I move the card, then I turn my back-image 90 degrees, so it goes out of view, and then I turn my front-image 90 degrees, so it comes into view.

The same implementation using GSAP’s timeline() function looks very similar:

var timeline = gsap.timeline({   onStart: function() {     $  (".status").text("Animating")   },   onComplete: function() {     $  (".status").text("Inactive")   } });  timeline.fromTo(".card", {   left: 0 }, {   duration: 0.5,   left: 300 }).fromTo(".card .back", {   rotationY: 0 }, {   rotationY: 90,   ease: "power1.easeIn",   duration: 0.2 }).fromTo(".card .front", {   rotationY: -90 }, {   rotationY: 0,   ease: "power1.easeOut",   duration: 0.2 })

Decision time

The main difference between Anime and GSAP appears to be the syntax, where GSAP might be a little more elaborate. I was stuck with two great libraries that had very similar functionality, were able to deal with complex animation, and had a thriving community. It seemed like I had a tie race!

Priority Anime GSAP
Smooth animations
Performance
Convenience
Browser support

So, what made me choose one library over the other?

I was very concerned about how the library would act under pressure. Having laggy animations in a game like Solitaire can greatly impact how fun it is to play the game. I knew I wouldn’t be able to fully see how the library performed before I created the game. Luckily, GSAP had made a stress test that compared different animation libraries to each other, including Anime.

Looking at that, GSAP certainly looked to be the superior library for dealing with loads of complex animations. GSAP was giving me upwards of 26 frames per second on a crazy heavy animation that Anime was only able to top out at 19.  After reading up on GSAP more and looking into their forums, it became clear that performance was of the highest priority to the guys behind GSAP.

And even though both GSAP and Anime have been around a while, Anime’s repo has been sitting somewhat dormant a couple of years while GSAP had made commits in the past couple of months.

I ended up using GSAP and haven’t regretted my decision!

How about you? Does any of this square with how you evaluate and compare front-end tooling? Are there other priorities you might have considered (e.g. accessibility, etc.) in a project like this? Or do you have a project where you had to pare down your choices from a bunch of different options? Please share in the comments because I’d like to know! 

Oh, and if you want to see how it looks when animating a whole deck of cards, you can head over to my site and play a game of Solitaire. Have fun!


How I Chose an Animation Library for My Solitaire Game originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

A Chrome Extension for Cloudinary That Helps You Pluck Out Useful Media URLs From Your Library Quickly

(This is a sponsored post.)

Cloudinary is a host for your digital assets like images and video. If you don’t already know them, you should, because you can build it into the asset management you almost certainly need to do if you run any size of website. Cloudinary helps you serve the assets as efficiently as technologically possible, meaning optimization, resizing, CDN hosting, and goes further in allowing interesting transforms on those assets.

If you already use it, unless you use it entirely through the APIs, you’ll know Cloudinary has a Media Library that gives you a UI dashboard for everything you’ve ever uploaded to Cloudinary. This is where you find your assets and open them up to play with the settings and transformations and such (e.g. blur it — then serve in the best possible format with automatic quality adjustments). You can always pop over to cloudinary.com to use this. But wouldn’t it be nice if this process was made a bit easier?

That clutch moment where you get the URL of the image you need.

There are all sorts of moments while bopping the web around doing our jobs as developers where you might need to get your fingers on an asset URL.

Gimme that URL!

Here’s a personal example: we have a little custom CMS thing for building our weekly email The CodePen Spark. It expects a URL to an image.

This is the exact kind of moment that the brand new Chrome Media Library Extension could help. Essentially it gives you a context menu you can use right in the browser to snag a URL to an asset. Right click, Insert and Asset URL.

It pops up a UI right inline (where you are on the web) of your Media Library, and you pick an image from there. Find the one you want, open it up, and you can either “edit” it to customize it to your liking, or just Insert it straight away.

Then it plops the URL right onto the site (probably an input) where you need it.

You can set up defaults to your liking, but I really like how the defaults are f_auto and q_auto which are Cloudinary classics that you’ll almost surely want. They mean “serve in the best possible format” and “optimize it intelligently”.

Sharon Yelenik introduced it on the Cloudinary blog:

Say your team creates social posts on a browser tab on an automated marketing application. To locate a media asset, you must open another tab to search for the asset within the Media Library, copy the related URL, and paste it into the app. In some cases, you even have to download an asset and then upload it into the app.

Talk about a classic example of menial, mundane, and repetitive chores!

Exactly. I like the idea of having tools to optimize workflows that should be easy. I’d also call Cloudinary a bit of a technical/developer tool, and there is an aspect to this that could be set up on anyone’s machine that would allow them to pick assets from your Media Library easily, without any access control worries.

If all this appeals to you:

Or see more at Cloudinary Labsdocumentation, and blog post.


A Chrome Extension for Cloudinary That Helps You Pluck Out Useful Media URLs From Your Library Quickly originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , , , , , , , ,
[Top]

Defining and Applying UI Themes Using the Mimcss CSS-in-JS Library

Theming UI refers to the ability to perform a change in visual styles in a consistent manner that defines the “look and feel” of a site. Swapping color palettes, à la dark mode or some other means, is a good example. From the user’s perspective, theming involves changing visual styles, whether it’s with UI for selecting a theme style, or the site automatically respecting the user’s color theme preference at the OS-level. From the developer’s perspective, tools used for theming should be easy-to-use and define themes at develop-time, before applying them at runtime.

This article describes how to approach theming with Mimcss, a CSS-in-JS library, using class inheritance—a method that should be intuitive for most developer as theming is usually about overriding CSS property values, and inheritance is perfect for those overrides.

Full discloser: I am the author of Mimcss. If you consider this a shameless promotion, you are not far from the truth. Nevertheless, I really do believe that the theming technique we’re covering in this article is unique, intuitive and worth exploring.

General theming considerations

Styling in web UI is implemented by having HTML elements reference CSS entities (classes, IDs, etc.). Since both HTML and CSS are dynamic in nature, changing visual representation can be achieved by one of the following methods:

  1. Changing the CSS selector of an HTML element, such as a different class name or ID.
  2. Changing actual CSS styling for that HTML element while preserving the selector.

Depending on the context, one method can be more efficient than another. Themes are usually defined by a limited number of style entities. Yes, themes are more than just a collection of colors and fonts—they can define paddings, margins, layouts, animations and so on . However, it seems that the number of CSS entities defined by a theme might be less than a number of HTML elements referencing these entities, especially if we are talking about heavy widgets such as tables, trees or code editors. With this assumption, when we want to change a theme, we’d rather replace style definitions than go over the HTML elements and (most likely) change the values of their class attributes.

Theming in plain CSS

In regular CSS, one way theming is supported is by using alternate stylesheets. This allows developers to link up multiple CSS files in the HTML <head>:

<link href="default.css" rel="stylesheet" type="text/css" title="Default Style"> <link href="fancy.css" rel="alternate stylesheet" type="text/css" title="Fancy"> <link href="basic.css" rel="alternate stylesheet" type="text/css" title="Basic">

Only one of the above stylesheets can be active at any given time and browsers are expected to provide the UI through which the user chooses a theme name taken from the values of the <link> element’s title attribute. The CSS rule names (i.e. class names) within the alternative stylesheets are expected to be identical, like:

/* default.css */ .element {   color: #fff; }  /* basic.css */ .element {   color: #333; }

This way, when the browser activates a different stylesheet, no HTML changes are required. The browser just recalculates styles (and layout) and repaints the page based on the “winning” values, as determined by The Cascade.

Alternate stylesheets, unfortunately, are not well-supported by mainstream browsers and, in some of them, work only with special extensions. As we will see later, Mimcss builds upon the idea of alternate stylesheets, but leverages it in a pure TypeScript framework.

Theming in CSS-in-JS

There are too many CSS-in-JS libraries out there, and there is no way we can completely cover how theming works in CSS-in-JS in a single post to do it justice. As far as CSS-in-JS libraries that are tightly integrated with React (e.g. Styled Components), theming is implemented on the ThemeProvider component and the Context API, or on the withTheme higher-order component. In both cases, changing a theme leads to re-rendering. As far as CSS-in-JS libraries that are framework-agnostic, theming is achieved via proprietary mechanisms, if theming is even supported at all.

The majority of the CSS-in-JS libraries—both React-specific and framework-agnostic—are focused on “scoping” style rules to components and thus are mostly concerned with creating unique names for CSS entities (e.g. CSS classes). In such environments, changing a theme necessarily means changing the HTML. This goes against the alternative stylesheets approach described above, in which theming is achieved by just changing the styles.

Here is where Mimcss library is different. It tries to combine the best of both theming worlds. On one hand, Mimcss follows the alternate stylesheets approach by defining multiple variants of stylesheets with identically named CSS entities. On the other hand, it offers the object-oriented approach and powerful TypeScript typing system with all the advantages of CSS-in-JS dynamic programming and type safety.

Theming in Mimcss

Mimcss is in that latter group of CSS-in-JS libraries in that it’s framework-agnostic. But it’s also created with the primary objective of allowing everything that native CSS allows in a type-safe manner, while leveraging the full power of the TypeScript’s typing system. In particular, Mimcss uses TypeScript classes to mimic the native CSS stylesheet files. Just as CSS files contain rules, the Mimcss Style Definition classes contain rules.

Classes open up the opportunity to use class inheritance to implement theming. The general idea is that a base class declares CSS rules used by the themes while derived classes provide different style property values for these rules. This is very similar to the native alternative stylesheets approach: activate a different theme class and, without any changes to the HTML code, the styles change.

But first, let’s very briefly touch on how styles are defined in Mimcss.

Mimcss basics

Stylesheets in Mimcss are modeled as Style Definition classes, which define CSS rules as their properties. For example:

import * as css from "mimcss"  class MyStyles extends css.StyleDefinition {   significant = this.$  class({     color: "orange",     fontStyle: "italic"   })    critical = this.$  id({     color: "red",     fontWeight: 700   }) }

The Mimcss syntax tries to be as close to regular CSS as possible. It is slightly more verbose, of course; after all, it is pure TypeScript that doesn’t require any plug-ins or pre-processing. But it still follows regular CSS patterns: for every rule, there is the rule name (e.g. significant), what type of rule it is (e.g. $ class), and the style properties the rule contains.

In addition to CSS classes and IDs, style definition properties can define other CSS rules, e.g. tags, keyframes, custom CSS properties, style rules with arbitrary selectors, media, @font-face, counters, and so on. Mimcss also supports nested rules including those with pseudo classes and pseudo-elements.

After a style definition class is defined, the styles should be activated:

let styles = css.activate(MyStyles);

Activating styles creates an instance of the style definition class and writes the CSS rules to the DOM. In order to use the styles, we reference the instance’s properties in our HTML rendering code:

render() {   return <div>     <p className={styles.significant.name}>       This is a significant paragraph.     </p>     <p id={styles.critical.name}>       This is a critical paragraph.     </p>   </div> }

We use styles.significant.name as a CSS class name. Note that the styles.significant property is not a string, but an object that has the name property and the CSS class name. The property itself also provides access to the CSS Object Model rule, which allows direct rule manipulation; this, however, is outside of the scope of this article (although Louis Lazaris has a great article on it).

If the styles are no longer needed, they can be deactivated which removes them from the DOM:

css.deactivate(styles);

The CSS class and ID names are uniquely generated by Mimcss. The generation mechanism is different in development and production versions of the library. For example, for the significant CSS class, the name is generated as MyStyles_significant in the development version, and as something like n2 in the production version. The names are generated when the style definition class is activated for the first time and they remain the same no matter how many times the class is activated and deactivated. How the names are generated depends on in what class they were first declared and this becomes very important when we start inheriting style definitions.

Style definition inheritance

Let’s look at a simple example and see what Mimcss does in the presence of inheritance:

class Base extends css.StyleDefinition {   pad4 = this.$  class({ padding: 4 }) } class Derived extends Base {   pad8 = this.$  class({ padding: 8 }) } let derived = css.activate(Derived);

Nothing surprising happens when we activate the Derived class: the derived variable provides access to both the pad4 and the pad8 CSS classes. Mimcss generates a unique CSS class name for each of these properties. The names of the classes are Base_pad4 and Derived_pad8 in the development version of the library.

Interesting things start happening when the Derived class overrides a property from the base class:

class Base extends css.StyleDefinition {   pad = this.$  class({ padding: 4 }) } class Derived extends Base {   pad = this.$  class({ padding: 8 }) } let derived = css.activate(Derived);

There is a single name generated for the derived.pad.name variable. The name is Base_pad; however, the style is { padding: 8px }. That is, the name is generated using the name of the base class, while the style is taken from the derived class.

Let’s try another style definition class that derives from the same Base class:

class AnotherDerived extends Base {   pad = this.$  class({ padding: 16 }) } let anotherDerived = css.activate(AnotherDerived);

As expected, the anotherDerived.pad.name has the value of Base_pad and the style is { padding: 16px }. Thus, no matter how many different derived classes we may have, they all use the same name for the inherited properties, but different styles are assigned to them. This is the key Mimcss feature that allows us to use style definition inheritance for theming.

Creating themes in Mimcss

The main idea of theming in Mimcss is to have a theme declaration class that declares several CSS rules, and to have multiple implementation classes that are derived from the declaration while overriding these rules by providing actual styles values. When we need CSS class names, as well as other named CSS entities in our code, we can use the properties from the theme declaration class. Then we can activate either this or that implementation class and, voilà, we can completely change the styling of our application with very little code.

Let’s consider a very simple example that nicely demonstrates the overall approach to theming in Mimcss.: a theme simply defines the shape and style of an element’s border.

First, we need to create the theme declaration class. Theme declarations are classes that derive from the ThemeDefinition class, which itself derives from the StyleDefinition class (there is an explanation why we need the ThemeDefinition class and why themes should not derive directly from the StyleDefinition class, but this is a topic for another day).

class BorderTheme extends css.ThemeDefinition {   borderShape = this.$  class() }

The BorderTheme class defines a single CSS class, borderShape. Note that we haven’t specified any styles for it. We are using this class only to define the borderShape property type, and let Mimcss create a unique name for it. In a sense, it is a lot like a method declaration in an interface—it declares its signature, which should be implemented by the derived classes.

Now let’s define two actual themes—using SquareBorderTheme and RoundBorderTheme classes—that derive from the BorderTheme class and override the borderShape property by specifying different style parameters.

class SquareBorderTheme extends BorderTheme {   borderShape = this.$  class({     border: ["thin", "solid", "green"],     borderInlineStartWidth: "thick"   }) }  class RoundBorderTheme extends BorderTheme {   borderShape = this.$  class({     border: ["medium", "solid", "blue"],     borderRadius: 8 // Mimcss will convert 8 to 8px   }) }

TypeScript ensures that the derived classes can only override a property using the same type that was declared in the base class which, in our case, is an internal Mimcss type used for defining CSS classes. That means that developers cannot use the borderShape property to mistakenly declare a different CSS rule because it leads to a compilation error.

We can now activate one of the themes as the default theme:

let theme: BorderTheme = css.activate(SquareBorderTheme);

When Mimcss first activates a style definition class, it generates unique names for all of CSS entities defined in the class. As we have seen before, the name generated for the borderShape property is generated once and will be reused when other classes deriving from the BorderTheme class are activated.

The activate function returns an instance of the activated class, which we store in the theme variable of type BorderTheme. Having this variable tells the TypeScript compiler that it has access to all the properties from the BorderTheme. This allows us to write the following rendering code for a fictional component:

render() {   return <div>     <input type="text" className={theme.borderShape.name} />   </div> }

All that is left to write is the code that allows the user to choose one of the two themes and activate it.

onToggleTheme() {   if (theme instanceof SquareBorderTheme)     theme = css.activate(RoundBorderTheme);   else     theme = css.activate(SquareBorderTheme); }

Note that we didn’t have to deactivate the old theme. One of the features of the ThemeDefinition class (as opposed to the StyleDefintion class) is that for every theme declaration class, it allows only a single theme to be active at the same time. That is, in our case, either RoundBorderTheme or SquareBorderTheme can be active, but never both. Of course, for multiple theme hierarchies, multiple themes can be simultaneously active. That is, if we have another hierarchy with the ColorTheme declaration class and the derived DarkTheme and LightTheme classes, a single ColorTheme-derived class can be co-active with a single BorderTheme-derived class. However, DarkTheme and LightTheme cannot be active at the same time.

Referencing Mimcss themes

In the example we just looked at, we used a theme object directly but themes frequently define elements like colors, sizes, and fonts that can be referenced by other style definitions. This is especially useful for separating the code that defines themes from the code that defines styles for a component that only wants to use the elements defined by the currently active theme.

CSS custom properties are perfect for declaring elements from which styles can be built. So, let’s define two custom properties in our themes: one for the foreground color, and one for the background color. We can also create a simple component and define a separate style definition class for it. Here is how we define the theme declaration class:

class ColorTheme extends css.ThemeDefinition {   bgColor = this.$  var( "color")   frColor = this.$  var( "color") }

The $ var method defines a CSS custom property. The first parameter specifies the name of the CSS style property, which determines acceptable property values. Note that we don’t specify the actual values here; in the declaration class, we only want Mimcss to create unique names for the custom CSS properties (e.g. --n13) while the values are specified in the theme implementation classes, which we do next.

class LightTheme extends ColorTheme {   bgColor = this.$  var( "color", "white")   frColor = this.$  var( "color", "black") }  class DarkTheme extendsBorderTheme {   bgColor = this.$  var( "color", "black")   frColor = this.$  var( "color", "white") }

Thanks to the Mimcss (and of course TypeScript’s) typing system, developers cannot mistakenly reuse, say, the bgColor property with a different type; nor they can specify values that are not acceptable for a color type. Doing so would immediately produce a compilation error, which may save developers quite a few cycles (one of the declared goals of Mimcss).

Let’s define styles for our component by referencing the theme’s custom CSS properties:

class MyStyles extends css.StyleDefinition {   theme = this.$  use(ColorTheme)    container = this.$  class({     color: this.theme.fgColor,     backgroundColor: this.theme.bgColor,   }) }

The MyStyles style definition class references the ColorTheme class by calling the Mimcss $ use method. This returns an instance of the ColorTheme class through which all its properties can be accessed and used to assign values to CSS properties.

We don’t need to write the var() function invocation because it’s already done by Mimcss when the $ var property is referenced. In effect, the CSS class for the container property creates the following CSS rule (with uniquely generated names, of course):

.container {   color: var(--fgColor);   backgroundColor: var(--bgColor); }

Now we can define our component (in pseudo-React style):

class MyComponent extends Component {   private styles = css.activate(MyStyles);    componentWillUnmount()   {     css.deactivate(this.styles);   }    render()   {     return <div className={this.styles.container.name}>       This area will change colors depending on a selected theme.     </div>   } }

Note one important thing in the above code: our component is completely decoupled from the classes that implement actual themes. The only class our component needs to know about is the theme declaration class ColorTheme. This opens a door to easily “externalize” creation of themes—they can be created by third-party vendors and delivered as regular JavaScript packages. As long as they derive from the ColorTheme class, they can be activated and our component reflects their values.

Imagine creating a theme declaration class for, say, Material Design styles along with multiple theme classes that derive from this class. The only caveat is that since we are using an existing system, the actual names of the CSS properties cannot be generated by Mimcss—they must be the exact names that the Material Design system uses (e.g. --mdc-theme--primary). Thankfully, for all named CSS entities, Mimcss provides a way to override its internal name generation mechanism and use an explicitly provided name. Here is how it can be done with Material Design CSS properties:

class MaterialDesignThemeBase extends css.ThemeDefinition {   primary = this.$  var( "color", undefined, "mdc-theme--primary")   onPrimary = this.$  var( "color", undefined, "mdc-theme--on-primary")   // ... }

The third parameter in the $ var call is the name, which is given to the CSS custom property. The second parameter is set to undefined meaning we aren’t providing any value for the property since this is a theme declaration, and not a concrete theme implementation.

The implementation classes do not need to worry about specifying the correct names because all name assignments are based on the theme declaration class:

class MyMaterialDesignTheme extends MaterialDesignThemeBase {   primary = this.$  var( "color", "lightslategray")   onPrimary = this.$  var( "color", "navy")   // ... }

Multiple themes on one page

As mentioned earlier, only a single theme implementation can be active from among the themes derived from the same theme declaration class. The reason is that different theme implementations define different values for the CSS rules with the same names. Thus, if multiple theme implementations were allowed to be active at the same time, we would have multiple definitions of identically-named CSS rules. This is, of course, a recipe for disaster.

Normally, having a single theme active at a time is not a problem at all—it is likely what we want in most cases. Themes usually define the overall look and feel of the entire page and there is no need to have different page sections to use different themes. What if, however, we are in that rare situation where we do need to apply different themes to different parts of our page? For example, what if before a user chooses a light or dark theme, we want to allow them to compare the two modes side-by-side?

The solution is based on the fact that custom CSS properties can be redefined under CSS rules. Since theme definition classes usually contain a lot of custom CSS properties, Mimcss provides an easy way to use their values from different themes under different CSS rules.

Let’s consider an example where we need to display two elements using two different themes on the same page. The idea is to create a style definition class for our component so that we could write the following rendering code:

public render() {   return <div>     <div className={this.styles.top.name}>       This should be black text on white background     </div>     <div className={this.styles.bottom.name}>       This should be white text on black background     </div>   </div> }

We need to define the CSS top and bottom classes so that we redefine the custom properties under each of them taking values from different themes. We essentially want to have the following CSS:

.block {   backgroundColor: var(--bgColor);   color: var(--fgColor); }  .block.top {   --bgColor: while;   --fgColor: black; }  .block.bottom {   --bgColor: black;   --fgColor: white; }

We use the block class for optimization purposes and to showcase how Mimcss handles inheriting CSS styles, but it is optional.

Here is how this is done in Mimcss:

class MyStyles extends css.StyleDefinition {   theme = this.$  use(ColorTheme)    block = this.$  class({     backgroundColor: this.theme.bgColor,     color: this.theme.fgColor   })    top = this.$  class({     "++": this.block,     "--": [LightTheme],   })    bottom = this.$  class({     "++": this.block,     "--": [DarkTheme],   }) }

Just as we did previously, we reference our ColorTheme declaration class. Then we define a helper block CSS class, which sets the foreground and background colors using the custom CSS properties from the theme. Then we define the top and bottom classes and use the "++" property to indicate that they inherit from the block class. Mimcss supports several methods of style inheritance; the "++" property simply appends the name of the referenced class to our class name. That is, the value returned by the styles.top.name is "top block" where we’re combining the two CSS classes (the actual names are randomly generated, so it would be something like "n153 n459").

Then we use the "--" property to set values of the custom CSS variables. Mimcss supports several methods of redefining custom CSS properties in a ruleset; in our case, we just reference a corresponding theme definition class. This causes Mimcss to redefine all custom CSS properties found in the theme class with their corresponding values.

What do you think?

Theming in Mimcss is intentionally based on style definition inheritance. We looked at exactly how this works, where we get the best of both theming worlds: the ability to use alternate stylesheets alongside the ability to swap out CSS property values using an object-oriented approach.

At runtime, Mimcss applies a theme without changing the HTML whatsoever. At build-time, Mimcss leverages the well-tried and easy-to-use class inheritance technique. Please check out the Mimcss documentation for a much deeper dive on the things we covered here. You can also visit the Mimcss Playground where you can explore a number of examples and easily try your own code.

And, of course, tell me what you think of this approach! This has been my go-to solution for theming and I’d like to continue making it stronger based on feedback from developers like yourself.


The post Defining and Applying UI Themes Using the Mimcss CSS-in-JS Library appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , ,
[Top]

Introduction to the Solid JavaScript Library

Solid is a reactive JavaScript library for creating user interfaces without a virtual DOM. It compiles templates down to real DOM nodes once and wraps updates in fine-grained reactions so that when state updates, only the related code runs.

This way, the compiler can optimize initial render and the runtime optimizes updates. This focus on performance makes it one of the top-rated JavaScript frameworks.

I got curious about it and wanted to give it a try, so I spent some time creating a small to-do app to explore how this framework handles rendering components, updating state, setting up stores, and more.

Here’s the final demo if you just can’t wait to see the final code and result:

Getting started

Like most frameworks, we can start by installing the npm package. To use the framework with JSX, run:

npm install solid-js babel-preset-solid

Then, we need to add babel-preset-solid to our Babel, webpack, or Rollup config file with:

"presets": ["solid"]

Or if you’d like to scaffold a small app, you can also use one of their templates:

# Create a small app from a Solid template npx degit solidjs/templates/js my-app   # Change directory to the project created cd my-app   # Install dependencies npm i # or yarn or pnpm   # Start the dev server npm run dev

There is TypeScript support so if you’d like to start a TypeScript project, change the first command to npx degit solidjs/templates/ts my-app.

Creating and rendering components

To render components, the syntax is similar to React.js, so it might seem familiar:

import { render } from "solid-js/web";   const HelloMessage = props => <div>Hello {props.name}</div>;   render(  () => <HelloMessage name="Taylor" />,  document.getElementById("hello-example") );

We need to start by importing the render function, then we create a div with some text and a prop, and we call render, passing the component and the container element.

This code then compiles down to real DOM expressions. For example, the code sample above, once compiled by Solid, looks something like this:

import { render, template, insert, createComponent } from "solid-js/web";   const _tmpl$   = template(`<div>Hello </div>`);   const HelloMessage = props => {  const _el$   = _tmpl$  .cloneNode(true);  insert(_el$  , () => props.name);  return _el$  ; };   render(  () => createComponent(HelloMessage, { name: "Taylor" }),  document.getElementById("hello-example") );

The Solid Playground is pretty cool and shows that Solid has different ways to render, including client-side, server-side, and client-side with hydration.

Tracking changing values with Signals

Solid uses a hook called createSignal that returns two functions: a getter and a setter. If you’re used to using a framework like React.js, this might seem a little weird. You’d normally expect the first element to be the value itself; however in Solid, we need to explicitly call the getter to intercept where the value is read in order to track its changes.

For example, if we’re writing the following code:

const [todos, addTodos] = createSignal([]);

Logging todos will not return the value, but a function instead. If we want to use the value, we need to call the function, as in todos().

For a small todo list, this would be:

import { createSignal } from "solid-js";   const TodoList = () => {  let input;  const [todos, addTodos] = createSignal([]);    const addTodo = value => {    return addTodos([...todos(), value]);  };    return (    <section>      <h1>To do list:</h1>      <label for="todo-item">Todo item</label>      <input type="text" ref={input} name="todo-item" id="todo-item" />      <button onClick={() => addTodo(input.value)}>Add item</button>      <ul>        {todos().map(item => (          <li>{item}</li>        ))}      </ul>    </section>  ); };

The code sample above would display a text field and, upon clicking the “Add item” button, would update the todos with the new item and display it in a list.

This can seem pretty similar to using useState, so how is using a getter different? Consider the following code sample:

console.log("Create Signals"); const [firstName, setFirstName] = createSignal("Whitney"); const [lastName, setLastName] = createSignal("Houston"); const [displayFullName, setDisplayFullName] = createSignal(true);   const displayName = createMemo(() => {  if (!displayFullName()) return firstName();  return `$  {firstName()} $  {lastName()}`; });   createEffect(() => console.log("My name is", displayName()));   console.log("Set showFullName: false "); setDisplayFullName(false);   console.log("Change lastName "); setLastName("Boop");   console.log("Set showFullName: true "); setDisplayFullName(true);

Running the above code would result in:

Create Signals   My name is Whitney Houston   Set showFullName: false   My name is Whitney   Change lastName   Set showFullName: true   My name is Whitney Boop

The main thing to notice is how My name is ... is not logged after setting a new last name. This is because at this point, nothing is listening to changes on lastName(). The new value of displayName() is only set when the value of displayFullName() changes, this is why we can see the new last name displayed when setShowFullName is set back to true.

This gives us a safer way to track values updates.

Reactivity primitives

In that last code sample, I introduced createSignal, but also a couple of other primitives: createEffect and createMemo.

createEffect

createEffect tracks dependencies and runs after each render where a dependency has changed.

// Don't forget to import it first with 'import { createEffect } from "solid-js";' const [count, setCount] = createSignal(0);   createEffect(() => {  console

Count is at... logs every time the value of count() changes.

createMemo

createMemo creates a read-only signal that recalculates its value whenever the executed code’s dependencies update. You would use it when you want to cache some values and access them without re-evaluating them until a dependency changes.

For example, if we wanted to display a counter 100 times and update the value when clicking on a button, using createMemo would allow the recalculation to happen only once per click:

function Counter() {    const [count, setCount] = createSignal(0);    // Calling `counter` without wrapping it in `createMemo` would result in calling it 100 times.    // const counter = () => {    //    return count();    // }      // Calling `counter` wrapped in `createMemo` results in calling it once per update. // Don't forget to import it first with 'import { createMemo } from "solid-js";'    const counter = createMemo(() => {        return count()    })      return (        <>        <button onClick={() => setCount(count() + 1)}>Count: {count()}</button>        <div>1. {counter()}</div>        <div>2. {counter()}</div>        <div>3. {counter()}</div>        <div>4. {counter()}</div>        <!-- 96 more times -->        </>    ); } 

Lifecycle methods

Solid exposes a few lifecycle methods, such as onMount, onCleanup and onError. If we want some code to run after the initial render, we need to use onMount:

// Don't forget to import it first with 'import { onMount } from "solid-js";'   onMount(() => {  console.log("I mounted!"); });

onCleanup is similar to componentDidUnmount in React — it runs when there is a recalculation of the reactive scope.

onError executes when there’s an error in the nearest child’s scope. For example we could use it when fetching data fails.

Stores

To create stores for data, Solid exposes createStore which return value is a readonly proxy object and a setter function.

For example, if we changed our todo example to use a store instead of state, it would look something like this:

const [todos, addTodos] = createStore({ list: [] });   createEffect(() => {  console.log(todos.list); });   onMount(() => {  addTodos("list", [    ...todos.list,    { item: "a new todo item", completed: false }  ]); });

The code sample above would start by logging a proxy object with an empty array, followed by a proxy object with an array containing the object {item: "a new todo item", completed: false}.

One thing to note is that the top level state object cannot be tracked without accessing a property on it — this is why we’re logging todos.list and not todos.

If we only logged todo` in createEffect, we would be seeing the initial value of the list but not the one after the update made in onMount.

To change values in stores, we can update them using the setting function we define when using createStore. For example, if we wanted to update a todo list item to “completed” we could update the store this way:

const [todos, setTodos] = createStore({  list: [{ item: "new item", completed: false }] });   const markAsComplete = text => {  setTodos(    "list",    i => i.item === text,    "completed",    c => !c  ); };   return (  <button onClick={() => markAsComplete("new item")}>Mark as complete</button> );

Control Flow

To avoid wastefully recreating all the DOM nodes on every update when using methods like .map(), Solid lets us use template helpers.

A few of them are available, such as For to loop through items, Show to conditionally show and hide elements, Switch and Match to show elements that match a certain condition, and more!

Here are some examples showing how to use them:

<For each={todos.list} fallback={<div>Loading...</div>}>  {(item) => <div>{item}</div>} </For>   <Show when={todos.list[0].completed} fallback={<div>Loading...</div>}>  <div>1st item completed</div> </Show>   <Switch fallback={<div>No items</div>}>  <Match when={todos.list[0].completed}>    <CompletedList />  </Match>  <Match when={!todos.list[0].completed}>    <TodosList />  </Match> </Switch>

Demo project

This was a quick introduction to the basics of Solid. If you’d like to play around with it, I made a starter project you can automatically deploy to Netlify and clone to your GitHub by clicking on the button below!

This project includes the default setup for a Solid project, as well as a sample Todo app with the basic concepts I’ve mentioned in this post to get you going!

There is much more to this framework than what I covered here so feel free to check the docs for more info!


The post Introduction to the Solid JavaScript Library appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

How I Used the WAAPI to Build an Animation Library

The Web Animations API lets us construct animations and control their playback with JavaScript. The API opens the browser’s animation engine to developers and was designed to underlie implementations of both CSS animations and transitions, leaving the door open to future animation effects. It is one of the most performant ways to animate on the Web, letting the browser make its own internal optimizations without hacks, coercion, or window.requestAnimationFrame().

With the Web Animations API, we can move interactive animations from stylesheets to JavaScript, separating presentation from behavior. We no longer need to rely on DOM-heavy techniques such as writing CSS properties and scoping classes onto elements to control playback direction. And unlike pure, declarative CSS, JavaScript also lets us dynamically set values from properties to durations. For building custom animation libraries and creating interactive animations, the Web Animations API might be the perfect tool for the job. Let’s see what it can do!

For the rest of this article, I will sometimes refer to the Web Animation API as WAAPI. When searching for resources on the Web Animation API, you might be led astray by searching “Web Animation API” so, to make it easy to find resources, I feel we should adopt the term WAAPI; tell me what you think in the comments below.

This is the library I made with the WAAPI

@okikio/animate is an animation library for the modern web. It was inspired by animateplus, and animejs; it is focused on performance and developer experience, and utilizes the Web Animation API to deliver butter-smooth animations at a small size, weighing in at ~5.79 KB (minified and gzipped).

The story behind @okikio/animate

In 2020, I decided to make a more efficient PJAX library, similar to Rezo Zero’sStarting Blocks project, but with the ease of use of barbajs. I felt starting blocks was easier to extend with custom functionality, and could be made smoother, faster, and easier to use.

Note: if you don’t know what a PJAX library is I suggest checking out MoOx/pjax; in short, PJAX allows for smooth transitions between pages using fetch requests and switching out DOM Elements.

Over time my intent shifted, and I started noticing how often sites from awwwards.com used PJAX, but often butchered the natural experience of the site and browser . Many of the sites looked cool at first glance, but the actual usage often told a different story — scrollbars were often overridden, prefetching was often too eager, and a lack of preparation for people without powerful internet connections, CPUs and/or GPUs. So, I decided to progressively enhance the library I was going to build. I started what I call the “native initiative” stored in the GitHub repo okikio/native; a means of introducing all the cool and modern features in a highly performant, compliant, and lightweight way.

For the native initiative I designed the PJAX library @okikio/native; while testing on an actual project, I ran into the Web Animation API, and realized there were no libraries that took advantage of it, so, I developed @okikio/animate, to create a browser compliant animation library. (Note: this was in 2020, around the same time use-web-animations by wellyshen was being developed. If you are using react and need some quick animate.css like effects, use-web-animations is a good fit.) At first, it was supposed to be simple wrapper but, little by little, I built on it and it’s now at 80% feature parity with more mature animation libraries.

Note: you can read more on the native initiative as well as the @okikio/native library on the Github repo okikio/native. Also, okikio/native, is a monorepo with @okikio/native and @okikio/animate being sub-packages within it.

Where @okikio/animate fits into this article

The Web Animation API is very open in design. It is functional on its own but it’s not the most developer-friendly or intuitive API, so I developed @okikio/animate to act as a wrapper around the WAAPI and introduce the features you know and love from other more mature animation libraries (with some new features included) to the high-performance nature of the Web Animation API. Give the project’s README a read for much more context.

Now, let’s get started

@okikio/animate creates animations by creating new instances of Animate (a class that acts as a wrapper around the Web Animation API).

import { Animate } from"@okikio/animate";  new Animate({   target: [/* ... */],   duration: 2000,   // ...  });

The Animate class receives a set of targets to animate, it then creates a list of WAAPI Animation instances, alongside a main animation (the main animation is a small Animation instance that is set to animate over a non-visible element, it exists as a way of tracking the progress of the animations of the various target elements), the Animate class then plays each target elements Animation instance, including the main animation, to create smooth animations.

The main animation is there to ensure accuracy in different browser vendor implementations of WAAPI. The main animation is stored in Animate.prototype.mainAnimation, while the target element’s Animation instances are stored in a WeakMap, with the key being its KeyframeEffect. You can access the animation for a specific target using the Animate.prototype.getAnimation(el).

You don‘t need to fully understand the prior sentences, but they will aid your understanding of what @okikio/animate does. If you want to learn more about how WAAPI works, check out MDN, or if you would like to learn more about the @okikio/animate library, I’d suggest checking out the okikio/native project on GitHub.

Usage, examples and demos

By default, creating a new instance of Animate is very annoying, so, I created the animate function, which creates new Animate instances every time it’s called.

import animate from "@okikio/animate"; // or import { animate } from "@okikio/animate";  animate({    target: [/* ... */],   duration: 2000,   // ...  });

When using the @okikio/animate library to create animations you can do this:

import animate from "@okikio/animate";  // Do this if you installed it via the script tag: const { animate } = window.animate;  (async () => {   let [options] = await animate({     target: ".div",      // Units are added automatically for transform CSS properties     translateX: [0, 300],     duration: 2000, // In milliseconds     speed: 2,   });    console.log("The Animation is done..."); })();

You can also play with a demo with playback controls:

Try out Motion Path:

Try different types of Motion by changing the Animation Options:

I also created a complex demo page with polyfills:

You can find the source code for this demo in the animate.ts and animate.pug files in the GitHub repo. And, yes, the demo uses Pug, and is a fairly complex setup. I highly suggest looking at the README as a primer for getting started.

The native initiative uses Gitpod, so if you want to play with the demo, I recommend clicking the “Open in Gitpod” link since the entire environment is already set up for you — there’s nothing to configure.

You can also check out some more examples in this CodePen collection I put together. For the most part, you can port your code from animejs to @okikio/animate with few-to-no issues.

I should probably mention that @okikio/animate supports both the target and targets keywords for settings animation targets. @okikio/animate will merge both list of targets into one list and use Sets to remove any repeated targets. @okikio/animate supports functions as animation options, so you can use staggering similar to animejs. (Note: the order of arguments are different, read more in the “Animation Options & CSS Properties as Methods” section of the README file.)

Restrictions and limitations

@okikio/animate isn’t perfect; nothing really is, and seeing as the Web Animation API is a living standard constantly being improved, @okikio/animate itself still has lots of space to grow. That said, I am constantly trying to improve it and would love your input so please open a new issue, create a pull request or we can have a discussion over at the GitHub project.

The first limitation is that it doesn’t really have a built-in timeline. There are a few reasons for this:

  1. I ran out of time. I am still only a student and don’t have lots of time to develop all the projects I want to.
  2. I didn’t think a formal timeline was needed, as async/await programming was supported. Also, I added timelineOffset as an animation option, should anyone ever need to create something similar to the timeline in animejs.
  3. I wanted to make @okikio/animate as small as possible.
  4. With group effects and sequence effects coming soon, I thought it would be best to leave the package small until an actual need comes up. On that note, I highly suggest reading Daniel C. Wilson’s series on the WAAPI, particularly the fourth installment that covers group effects and sequence effects.

Another limitation of @okikio/animate is that it lacks support for custom easings, like spring, elastic, etc. But check out Jake Archibald’s proposal for an easing worklet. He discusses multiple standards that are currently in discussion. I prefer his proposal, as it’s the easiest to implement, not to mention the most elegant of the bunch. In the meanwhile, I’m taking inspiration from Kirill Vasiltsov article on Spring animations with WAAPI and I am planning to build something similar into the library.

The last limitation is that @okikio/animate only supports automatic units on transform functions e.g. translateX, translate, scale, skew, etc. This is no longer the case as of @okikio/animate@2.2.0, but there are still some limitations on CSS properties that support color. Check the GitHub release for more detail.

For example:

animate({   targets: [".div", document.querySelectorAll(".el")],    // By default "px", will be applied   translateX: 300,   left: 500,   margin: "56 70 8em 70%",    // "deg" will be applied to rotate instead of px   rotate: 120,     // No units will be auto applied   color: "rgb(25, 25, 25)",   "text-shadow": "25px 5px 15px rgb(25, 25, 25)" });

Looking to the future

Some future features, like ScrollTimeline, are right around the corner. I don’t think anyone actually knows when it will release but since the ScrollTimeline in Chrome Canary 92, I think it’s safe to say the chances of a release in the near future look pretty good.

I built the timeline animation option into @okikio/animate to future-proof it. Here’s an example:

Thanks to Bramus for the demo inspiration! Also, you may need the Canary version of Chrome or need to turn on Experimental Web Platform features in Chrome Flags to view this demo. It seems to work just fine on Firefox, though, so… 🤣.

If you want to read more on the ScrollTimeline, Bramus wrote an excellent article on it. I would also suggest reading the Google Developers article on Animation Worklets.

My hope is to make the library smaller. It’s currently ~5.79 KB which seems high, at least to me. Normally, I would use a bundlephobia embed but that has trouble bundling the project, so if you want to verify the size, I suggest using bundle.js.org because it actually bundles the code locally on your browser. I specifically built it for checking the bundle size of @okikio/animate, but note it’s not as accurate as bundlephobia.

Polyfills

One of the earlier demos shows polyfills in action. You are going to need web-animations-next.min.js from web-animations-js to support timelines. Other modern features the KeyframeEffect constructor is required.

The polyfill uses JavaScript to test if the KeyframeEffect is supported and, if it isn’t, the polyfill loads and does its thing. Just avoid adding async/defer to the polyfill, or it will not work the way you expect. You’ll also want to polyfill Map, Set, and Promise.

<html>   <head>     <!-- Async -->     <script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=default,es2015,es2018,Array.prototype.includes,Map,Set,Promise" async></script>     <!-- NO Async/Defer -->     <script src="./js/webanimation-polyfill.min.js"></script>   </head>   <body>     <!-- Content -->   </body> </html>

And if you’re building for ES6+, I highly recommend using esbuild for transpiling, bundling, and minifying. For ES5, I suggest using esbuild (with minify off), Typescript (with target of ES5), and terser; as of now, this is the fastest setup to transpile to ES5, it’s faster and more reliable than babel. See the Gulpfile from the demo for more details.

Conclusion

@okikio/animate is a wrapper around the Web Animation API (WAAPI) that allows you to use all the features you love from animejs and other animation libraries, in a small and concise package. So, what are your thoughts after reading about it? Is it something you think you’ll reach for when you need to craft complex animations? Or, even more important, is there something that would hold you back from using it? Leave a comment below or join the discussion on Github Discussions.


This article originally appeared on dev.to, it also appeared on hackernoon.com and hashnode.com.
Photo by Pankaj Patel on Unsplash.


The post How I Used the WAAPI to Build an Animation Library appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Testing React Hooks With Enzyme and React Testing Library

As you begin to make use of React hooks in your applications, you’ll want to be certain the code you write is nothing short of solid. There’s nothing like shipping buggy code. One way to be certain your code is bug-free is to write tests. And testing React hooks is not much different from how React applications are tested in general.

In this tutorial, we will look at how to do that by making use of a to-do application built with hooks. We’ll cover writing of tests using Ezyme and React Testing Library, both of which are able to do just that. If you’re new to Enzyme, we actually posted about it a little while back showing how it can be used with Jest in React applications. It’s not a bad idea to check that as we dig into testing React hooks.

Here’s what we want to test

A pretty standard to-do component looks something like this:

import React, { useState, useRef } from "react"; const Todo = () => {   const [todos, setTodos] = useState([     { id: 1, item: "Fix bugs" },     { id: 2, item: "Take out the trash" }   ]);   const todoRef = useRef();   const removeTodo = id => {     setTodos(todos.filter(todo => todo.id !== id));   };   const addTodo = data => {     let id = todos.length + 1;     setTodos([       ...todos,       {         id,         item: data       }     ]);   };   const handleNewTodo = e => {     e.preventDefault();     const item = todoRef.current;     addTodo(item.value);     item.value = "";   };   return (     <div className="container">       <div className="row">         <div className="col-md-6">           <h2>Add Todo</h2>         </div>       </div>       <form>         <div className="row">           <div className="col-md-6">             <input               type="text"               autoFocus               ref={todoRef}               placeholder="Enter a task"               className="form-control"               data-testid="input"             />           </div>         </div>         <div className="row">           <div className="col-md-6">             <button               type="submit"               onClick={handleNewTodo}               className="btn btn-primary"             >               Add Task             </button>           </div>         </div>       </form>       <div className="row todo-list">         <div className="col-md-6">           <h3>Lists</h3>           {!todos.length ? (             <div className="no-task">No task!</div>           ) : (             <ul data-testid="todos">               {todos.map(todo => {                 return (                   <li key={todo.id}>                     <div>                       <span>{todo.item}</span>                       <button                         className="btn btn-danger"                         data-testid="delete-button"                         onClick={() => removeTodo(todo.id)}                       >                         X                       </button>                     </div>                   </li>                 );               })}             </ul>           )}         </div>       </div>     </div>   ); }; export default Todo; 

Testing with Enzyme

We need to install the packages before we can start testing. Time to fire up the terminal!

npm install --save-dev enzyme enzyme-adapter-16 

Inside the src directory, create a file called setupTests.js. This is what we’ll use to configure Enzyme’s adapter.

import Enzyme from "enzyme"; import Adapter from "enzyme-adapter-react-16"; Enzyme.configure({ adapter: new Adapter() }); 

Now we can start writing our tests! We want to test four things:

  1. That the component renders
  2. That the initial to-dos get displayed when it renders
  3. That we can create a new to-do and get back three others
  4. That we can delete one of the initial to-dos and have only one to-do left

In your src directory, create a folder called __tests__ and create the file where you’ll write your Todo component’s tests in it. Let’s call that file Todo.test.js.

With that done, we can import the packages we need and create a describe block where we’ll fill in our tests.

import React from "react"; import { shallow, mount } from "enzyme"; import Todo from "../Todo";  describe("Todo", () => {   // Tests will go here using `it` blocks });

Test 1: The component renders

For this, we’ll make use of shallow render. Shallow rendering allows us to check if the render method of the component gets called — that’s what we want to confirm here because that’s the proof we need that the component renders.

it("renders", () => {   shallow(<Todo />); });

Test 2: Initial to-dos get displayed

Here is where we’ll make use of the mount method, which allows us to go deeper than what shallow gives us. That way, we can check the length of the to-do items.

it("displays initial to-dos", () => {   const wrapper = mount(<Todo />);   expect(wrapper.find("li")).toHaveLength(2); });

Test 3: We can create a new to-do and get back three others

Let’s think about the process involved in creating a new to-do:

  1. The user enters a value into the input field.
  2. The user clicks the submit button.
  3. We get a total of three to-do items, where the third is the newly created one.
it("adds a new item", () => {   const wrapper = mount(<Todo />);   wrapper.find("input").instance().value = "Fix failing test";   expect(wrapper.find("input").instance().value).toEqual("Fix failing test");   wrapper.find('[type="submit"]').simulate("click");   expect(wrapper.find("li")).toHaveLength(3);   expect(     wrapper       .find("li div span")       .last()       .text()   ).toEqual("Fix failing test"); });

We mount the component then we make use of find() and instance() methods to set the value of the input field. We assert that the value of the input field is set to “Fix failing test” before going further to simulate a click event, which should add the new item to the to-do list.

We finally assert that we have three items on the list and that the third item is equal to the one we created.

Test 4: We can delete one of the initial to-dos and have only one to-do left

it("removes an item", () => {   const wrapper = mount(<Todo />);   wrapper     .find("li button")     .first()     .simulate("click");   expect(wrapper.find("li")).toHaveLength(1);   expect(wrapper.find("li span").map(item => item.text())).toEqual([     "Take out the trash"   ]); });

In this scenario, we return the to-do with a simulated click event on the first item. It’s expected that this will call the removeTodo() method, which should delete the item that was clicked. Then we’re checking the numbers of items we have, and the value of the one that gets returned.

The source code for these four tests are here on GitHub for you to check out.

Testing With react-testing-library

We’ll write three tests for this:

  1. That the initial to-do renders
  2. That we can add a new to-do
  3. That we can delete a to-do

Let’s start by installing the packages we need:

npm install --save-dev @testing-library/jest-dom @testing-library/react

Next, we can import the packages and files:

import React from "react"; import { render, fireEvent } from "@testing-library/react"; import Todo from "../Todo"; import "@testing-library/jest-dom/extend-expect";  test("Todo", () => {   // Tests go here }

Test 1: The initial to-do renders

We’ll write our tests in a test block. The first test will look like this:

it("displays initial to-dos", () => {   const { getByTestId } = render(<Todo />);   const todos = getByTestId("todos");   expect(todos.children.length).toBe(2); });

What’s happening here? We’re making use of getTestId to return the node of the element where data-testid matches the one that was passed to the method. That’s the <ul> element in this case. Then, we’re checking that it has a total of two children (each child being a <li> element inside the unordered list). This will pass as the initial to-do is equal to two.

Test 2: We can add a new to-do

We’re also making use of getTestById here to return the node that matches the argument we’re passing in.

it("adds a new to-do", () => {   const { getByTestId, getByText } = render(<Todo />);   const input = getByTestId("input");   const todos = getByTestId("todos");   input.value = "Fix failing tests";   fireEvent.click(getByText("Add Task"));   expect(todos.children.length).toBe(3); });

We use getByTestId to return the input field and the ul element like we did before. To simulate a click event that adds a new to-do item, we’re using fireEvent.click() and passing in the getByText() method, which returns the node whose text matches the argument we passed. From there, we can then check to see the length of the to-dos by checking the length of the children array.

Test 3: We can delete a to-do

This will look a little like what we did a little earlier:

it("deletes a to-do", () => {   const { getAllByTestId, getByTestId } = render(<Todo />);   const todos = getByTestId("todos");   const deleteButton = getAllByTestId("delete-button");   const first = deleteButton[0];   fireEvent.click(first);   expect(todos.children.length).toBe(1); });

We’re making use of getAllByTestId to return the nodes of the delete button. Since we only want to delete one item, we fire a click event on the first item in the collection, which should delete the first to-do. This should then make the length of todos children equal to one.

These tests are also available on GitHub.

Linting

There are two lint rules to abide by when working with hooks:

Rule 1: Call hooks at the top level

…as opposed to inside conditionals, loops or nested functions.

// Don't do this! if (Math.random() > 0.5) {   const [invalid, updateInvalid] = useState(false); }

This goes against the first rule. According to the official documentation, React depends on the order in which hooks are called to associate state and the corresponding useState call. This code breaks the order as the hook will only be called if the conditions are true.

This also applies to useEffect and other hooks. Check out the documentation for more details.

Rule 2: Call hooks from React functional components

Hooks are meant to be used in React functional components — not in React’s class component or a JavaScript function.

We’ve basically covered what not to do when it comes to linting. We can avoid these missteps with an npm package that specifically enforces these rules.

npm install eslint-plugin-react-hooks --save-dev

Here’s what we add to the package’s configuration file to make it do its thing:

{   "plugins": [     // ...     "react-hooks"   ],   "rules": {     // ...     "react-hooks/rules-of-hooks": "error",     "react-hooks/exhaustive-deps": "warn"   } }

If you are making use of Create React App, then you should know that the package supports the lint plugin out of the box as of v3.0.0.

Go forth and write solid React code!

React hooks are equally prone to error as anything else in your application and you’re gonna want to ensure that you use them well. As we just saw, there’s a couple of ways we can go about it. Whether you use Enzyme or You can either make use of enzyme or React Testing Library to write tests is totally up to you. Either way, try making use of linting as you go, and no doubt, you’ll be glad you did.

The post Testing React Hooks With Enzyme and React Testing Library appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Getting Started with React Testing Library

I can guess what you are thinking: another React testing library? So many have already been covered here on CSS-Tricks (heck, I’ve already posted one covering Jest and Enzyme) so aren’t there already enough options to go around?

But react-testing-library is not just another testing library. It’s a testing library, yes, but one that’s built with one fundamental principle that separates it from the rest.

The more your tests resemble the way your software is used, the more confidence they can give you.

It tries to address tests for how a user will use your application. In fact, it’s done in such a way that tests won’t break even when you refactor components. And I know that’s something we’ve all run into at some point in our React journey.

We’re going to spend some time writing tests together using react-testing-library for a light to-do application I built. You can clone the repo locally:

git clone https://github.com/kinsomicrote/todoapp-test.git

And, if you do that, install the required packages next:

## yarn yarn add --dev react-testing-library jest-dom  ## npm npm install --save-dev react-testing-library jest-dom

In case you’re wondering why Jest is in there, we’re using it for assertion. Create a folder called __test__ inside the src directory and create a new file called App.test.js.

Taking snapshots

Snapshot tests keep a record of tests that have been performed on a tested component as a way to visually see what’s changes between changes.

When we first run this test, we take the first snapshot of how the component looks. As such, the first test is bound to pass because, well, there’s no other snapshot to compare it to that would indicate something failed. It only fails when we make a new change to the component by adding a new element, class, component, or text. Adding something that was not there when the snapshot was either created or last updated.

The snapshot test will be the first test we will be writing here. Let’s open the App.test.js file and make it look like this:

import React from 'react'; import { render, cleanup } from "react-testing-library"; import "jest-dom/extend-expect"; import App from './App';  afterEach(cleanup);  it("matches snapshot", () => {   const { asFragment } = render(<App />);   expect(asFragment()).toMatchSnapshot(); });

This imports the necessary packages we are using to write and run the tests. render is used to display the component we want to test. We make use of cleanup to clear things out after each test runs — as you can see with the afterEach(cleanup) line.

Using asFragment, we get a DocumentFragment of the rendered component. Then we expect it to match the snapshot that had been created.

Let’s run the test to see what happens:

## yarn yarn test  ## npm npm test

As we now know, a snapshot of the component gets created in a new folder called __snapshots__ inside the __tests__ directory if this is our first test. We actually get a file called App.test.js.snap in there that will look like this:

// Jest Snapshot v1, https://goo.gl/fbAQLP  exports[`matches snapshot 1`] = ` <DocumentFragment>   <div     class="container"   >     <div       class="row"     >       <div         class="col-md-6"       >         <h2>           Add Todo         </h2>       </div>     </div>     <form>       <div         class="row"       >         <div           class="col-md-6"         >           <input             class="form-control"             data-testid="todo-input"             placeholder="Enter a task"             type="text"             value=""           />         </div>       </div>       <div         class="row"       >         <div           class="col-md-6"         >           <button             class="btn btn-primary"             data-testid="add-task"             type="submit"           >             Add Task           </button>         </div>       </div>     </form>     <div       class="row todo-list"     >       <div         class="col-md-6"       >         <h3>           Lists         </h3>         <ul           data-testid="todos-ul"         >           <li>             <div>               Buy Milk               <button                 class="btn btn-danger"               >                 X               </button>             </div>           </li>           <li>             <div>               Write tutorial               <button                 class="btn btn-danger"               >                 X               </button>             </div>           </li>         </ul>       </div>     </div>   </div> </DocumentFragment> `;

Now, let’s Test DOM elements and events

Our app includes two to-do items that display by default the first time the app runs. We want to make sure that they do, in fact, show up by default on the first app run so, to test this, we have to target the unordered list (<ul>) and check the length. We expect the length to be equal to two — the number of items.

it('it displays default todo items', () => {   const { getByTestId } = render(<App />);   const todoList = getByTestId('todos-ul');   expect(todoList.children.length).toBe(2);   });

We’re making use of getByTestId in that snippet to extract the test IDs from the App component. We then set todoList to target the todos-ul element. That’s what should return as two.

Using what we’ve learned so far, see if you can write a test to assert that a user can enter values in the input field. Here are the things you’ll want to do:

  • Get the input field
  • Set a value for the input field
  • Trigger a change event
  • Assert that the input field has its value as the one you set for it in Step 2

Don’t peek at my answer below! Take as much time as you need.

Still going? Great! I’ll go grab some coffee and be right back.

Mmm, coffee. ☕️

Oh, you’re done! You rock. Let’s compare answers. Mine looks like this:

it('allows input', () => {   const {getByTestId } = render(<App />)   let item = 'Learn React'   const todoInputElement = getByTestId('todo-input');   todoInputElement.value = item;   fireEvent.change(todoInputElement);   expect(todoInputElement.value).toBe('Learn React') });

Using getByTestId, I am able to extract the test IDs in the application. Then I create a variable which is set to the string Learn React, and make it the value of the input field. Next, I obtain the input field using its test ID and fire the change event after setting the value of the input field. With that done, I assert that the value of the input field is indeed Learn React.

Does that check out with your answer? Leave a comment if you have another way of going about it!

Next, let’s test that we can add a new to-do item. We’ll need to get the input field, the button for adding new items and the unordered list because those are all of the elements needed to create an new item.

We set a value for the input field and then trigger a button click to add the task. We’re able to do this by obtaining the button using getByText — by triggering a click event on the DOM element with the text Add Task, we should be able to add a new to-do item.

Let’s assert that the number of children (list items) in unordered list element is equal to three. This assumes that the default tasks are still in tact.

it('adds a new todo item', () => {   const { getByText, getByTestId } = render(<App />);   const todoInputElement = getByTestId('todo-input');   const todoList = getByTestId('todos-ul');   todoInputElement.value = 'Learn React';   fireEvent.change(todoInputElement);   fireEvent.click(getByText('Add Task'))   expect(todoList.children.length).toBe(3);  });

Pretty nice, right?

This is just one way to test in React

You can try react-testing-library in your next React application. The documentation in the repo is super thorough and — like most tools — the best place to start. Kent C. Dodds built it and has a full course on testing over at Frontend Masters (subscription required) that also covers the ins and outs of react-testing-library.

That said, this is just one testing resource for React. There are others, of course, but hopefully this is one you’re interested in trying out now that you’ve seen a bit of it but use what’s best for your project, of course.

The post Getting Started with React Testing Library appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]