React Spectrum – A collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.

By Adobe:

React Spectrum includes three libraries:

  • React Spectrum β€” A React implementation of Spectrum, Adobe’s design system.
  • React Aria β€” A library of React Hooks that provides accessible UI primitives for your design system.
  • React Stately β€” A library of React Hooks that provides cross-platform state management and core logic for your design system.

React Spectrum →

How Records & Tuples will improve React

Covered here before are Records and Tuples, two Immutable Datastructures most likely coming to JavaScript. Sebastien Lorber takes a look at how these will improve React.

A whole category of React bugs are related to unstable object identities:

  • Performance: can trigger re-renders that could be avoided
  • Behavior: can trigger useless effect re-executions, and lead to infinite loops
  • API surface: we don’t have a way to express when a stable object identity matters

I will explain the basics of Records & Tuples, and how they can solve real world React issues.

Records & Tuples for React →

useQueryParams – A React Hook for managing state in URL query parameters

Once you’ve set up a QueryParamProvider high up your component tree, you can start using useQueryParam to get (and set) querystring parameters

import * as React from 'react';
import { useQueryParam, NumberParam, StringParam } from 'use-query-params';

const UseQueryParamExample = () => {
  // something like: ?x=123&foo=bar in the URL
  const [num, setNum] = useQueryParam('x', NumberParam);
  const [foo, setFoo] = useQueryParam('foo', StringParam);

  return (
    <div>
      <h1>num is {num}</h1>
      <button onClick={() => setNum(Math.random())}>Change</button>
      <h1>foo is {foo}</h1>
      <button onClick={() => setFoo(`str${Math.random()}`)}>Change</button>
    </div>
  );
};

export default UseQueryParamExample;

Also ships with an HOC and Render Props approaches.

useQueryParams

You Can’t Stop Us | Nike

😲 Stunning editing work!

tinykeys – A tiny and modern library for keybindings

Small and lightweight, I like:

import tinykeys from "tinykeys"

tinykeys(window, {
  "Shift+D": () => {
    alert("The 'Shift' and 'd' keys were pressed at the same time")
  },
  "y e e t": () => {
    alert("The keys 'y', 'e', 'e', and 't' were pressed in order")
  },
  "$mod+KeyD": () => {
    alert("Either 'Control+d' or 'Meta+d' were pressed")
  },
});

When calling tinykeys it returns an unsubscribe function. Handy when developing components:

import { useEffect } from "react"
import tinykeys from "tinykeys"

function useKeyboardShortcuts() {
  useEffect(() => {
    let unsubscribe = tinykeys(window, {
      // ...
    });
    return () => {
      unsubscribe(); // make use of the returned unsubscribe() function.
    }
  })
}

Installation per NPM/Yarn

npm install tinykeys

tinykeys →
tinykeys Source (GitHub) →

ESNext: Declarations in Conditionals (Stage-1)

An ECMAScript Language Feature that I’m looking forward to is Declarations in Conditionals, which adds the capability to declare variables inside conditional statements.

~

Setting the Scene

Say you want to iterate over a certain set that is tucked away inside an object

if (user.meta.interests) {
    for (let interest of user.meta.interests) {
        // …
    }
}

To not have to type user.meta.interests twice you might declare an intermediary variable, and use that further down in your code:

let interests = user.meta.interests;
if (interests) {
    for (let interest of interests) {
        // …
    }
}

But now you have a nasty side-effect: you’re loitering the scope with this extra interests variable. This is quite useless, as you’re only using it inside the if statement.

~

Hello “Declarations in Conditionals”

With the “Declarations in Conditionals” proposal we can keep this extra interests variable, yet without loitering the outer scope. We do this by declaring the variable directly inside the conditional (e.g. if statement), like so:

if (let interests = user.meta.interests) {
    for (let interest of interests) {
        // …
    }
}

⚠️ Note that this is an assignment right there, not an equality check!

With that one line the interests variable:

  1. will be declared
  2. will have a value assigned to it
  3. will be checked for being falsy or not

Scope-wise the interests variable will have its scope limited local to the conditional (e.g. only visible inside the if).

The proposal limits declarations to use either const or let; declarations in conditionals with var are currently not allowed.

πŸ’‘ You might not 100% realize it, but you’re already doing this kind of thing when using a for loop. Inside its initialization part (e.g. its first part) you are also declaring variables.

for (let i = 0; i < 5; i++) {
  console.log(i);
}

☝️ Did you know that you can declare more than one variable there, separated by a comma? In the example below I declare an extra variable len right after declaring i:

const cars = ['Ford', 'Mercedes', 'BMW'];

for (let i = 0, len = cars.length; i < len; i++) {
  console.log(cars[i]);
}

πŸ€” What about the scope of len you might ask? Variables declared using let are scoped to the statement (e.g. local to the loop). Variables declared using var get hoisted. And oh, it’s not allowed to declare variables using const here.

~

So, when can we use this?

The proposal is currently Stage-1, so it still has a long way to go before it can become an official part of the standard β€” if it ever will be.

πŸ’β€β™‚οΈ Stage-1?

The Technical Committee which is concerned with the standardization of ECMAScript (e.g. TC39) has a 5 stage process in place, ranging from stage-0 to stage-4, by which it develops a new language feature.

Stage-1 is the Proposal stage. It signals that the committee is showing interest in the proposal and that it is seeking further investigation on how to tackle it. At this stage the proposal is subject to heavy changes. It is only when a proposal reaches Stage 4 that it is ready to become part of the ECMAScript Specification.

As it’s still Stage-1, it can change quite a lot before it to be finished. And I’m quite sure it will change, as right now:

  • It only allows declaring one variable.
  • It only checks for values being falsy or not.

Must say I’m pretty curious how this one will evolve, as I’m already quite excited about it: it’s one of those small additions that will have a pretty big impact on how I write my code.

If you have an addition to this proposal, feel free to join the discussion in the Declarations in Conditionals GitHub repository.

~

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would always put a smile on my face. Thanks!

β˜•οΈ Buy me a Coffee (€3)

Native Image Lazy-Loading: loading-attribute-eagle-polyfill

Today, Rick Viscomi noted that some sites have set eagle – instead of eager – as the value for Native Image Lazy-Loading:

While this is most likely a classic case of #damnyouautocorrect (instead of jokingly being a LOTR/Scrubs reference), that didn’t keep Jay Phelps from creating loading-attribute-eagle-polyfill to cater for those small mishaps:

A polyfill for <img loading="eagle" />. Displays an America Eagle as the placeholder of the image while the your real images are still loading.

LOL 😁 β€” I love the internet.

Here’s a code example on how to use, if you ever were to use it in the first place:

<head>
  <script src="https://unpkg.com/loading-attribute-eagle-polyfill/loading-attribute-eagle-polyfill.js"></script>
</head>
<body>
  <!-- Here's an example URL that artificially delays the src so you can see the proud Eagle -->
  <img
    loading="eagle"
    src="https://deelay.me/2000/https://img.webmd.com/dtmcms/live/webmd/consumer_assets/site_images/article_thumbnails/other/cat_relaxing_on_patio_other/1800x1200_cat_relaxing_on_patio_other.jpg"
    width="300"
    height="200"
  />
</body>

loading-attribute-eagle-polyfill

ℹ️ Remember Native Image Lazy Loading being way too eager? Chrome recently updated the thresholds and are backporting the changes back to Chrome version 79:

Simple Image Gallery with display: grid; and object-fit: cover;

On the Full Stack Belgium Slack channel, user @Brammm recently asked how to create a simple image gallery.

Anyone have a favorite way of making an image grid with CSS? I’d like one of those β€œfancy” ones where no matter the aspect ratio of the image, they all have the same gap between them.

While some suggested existing solutions such as React Photo Gallery or photo-stream, I focussed on only the layout-question and suggested a DIY solution using display: grid; and object-fit: cover;.

Using only those those two props it’s really easy to create a grid based gallery.

  • Using display: grid; you create a grid layout. I decided to create a grid of square cells/tiles.
  • As I stretch out each image inside a tile, the images can get deformed. Using object-fit: cover; this can be prevented. Note that you will visually lose some data, as the edges will get clipped, but that was not a concern to @Brammm.

~

Proof Of Concept

Using photos from the birth of my son Noah I knocked up a small small proof of concept in about 10 minutes. Tweaking it a bit more – by adding some CSS Variables into the mix – I eventually landed on this:

See the Pen
Simple Gallery (display: grid; + object-fit: cover;) V1
by Bramus (@bramus)
on CodePen.

πŸ’β€β™‚οΈ To create a consistent gap between all items I used the CSS gap property, which replaces grid-gap.

~

Making it feel more dynamic

To make things visually more interesting, and also since some photos are portrait and some landscape, the size of the tiles must differ a bit. With CSS Grid in place it’s really easy to stretch out cells so that they span more than one column or row:

ul.gallery > li.wide {
    grid-column: span 2;
}

ul.gallery > li.wider {
    grid-column: span 3;
}

ul.gallery > li.high {
    grid-row: span 2;
    height: auto; /* to undo the height */
}

With these in place, my gallery started to look like this:

There, looks good, right? πŸ™‚

πŸ˜‹ I know, I cheated a bit as I added the .wide/.high classes onto carefully selected tiles, leaving no gaps in my grid. To workaround potential gaps, one can always use grid-auto-flow: dense;, or use the (still under development) Grid Masonry from the Grid Level 2 spec. Note that in those cases the order of the images will differ from their source order.

~

Going further: Zoom / Lightbox functionality

Where I had originally stopped working on the gallery after that, today I wondered if I could adjust it a bit further and give it the ability to show the images at full screen, in some sort of Lightbox. Still using only CSS it’s possible to show an overlay while pressing+holding a tile.

  • Using the :active pseudo-selector you can know which element is currently being pressed
  • Using position: fixed; you can put content on top of the entire viewport

Combining the two as follows …

ul.gallery > li:active > img {
    position: fixed; /* Position the image on top of the viewport contents */
    z-index: 11;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    object-fit: contain; /* Make sure the image isn't distorted */
    padding: 1vw; /* Add some padding to make it breathe a bit */
    background: rgba(0, 0, 0, 0.8); /* Dim the background */
}

… will get you this:

See the Pen
Simple Gallery (display: grid; + object-fit: cover;) V3 (WIP)
by Bramus (@bramus)
on CodePen.

While the version above does work (on Desktop), there’s a huge problem with it: it’s not (keyboard) accessible at all. In order to give the browser – and therefore user – hints about stuff being clickable we could add a truckload of ARIA attributes or simply use … links. An extra benefit of this approach is that we then start using CSS :target, and eventually create Pure CSS Lightbox.

See the Pen
Simple Gallery (display: grid; + object-fit: cover;) V4b
by Bramus (@bramus)
on CodePen.

It’s not entirely perfect though, when using only the keyboard to navigate, you have to do some trickery to close the Lightbox: after zooming in on an image you’ll have to hit TAB to focus the close link and then hit ENTER to activate it β€” If only autofocus where available on non-inputs …

As an extra I also added some JS to make the ←, β†’, and ESC keys work, so that it behaves like a β€œreal” Lightbox. There’s plenty more things I could add to it, but that’s something way beyond the original goal of this post so I left it at that.

~

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would always put a smile on my face. Thanks!

β˜•οΈ Buy me a Coffee (€3)