Building a Reaction Component
Tags: html, css, javascript, webdev
Published: 2021-07-17 08:24:00

We're all familiar with dev.to's “Reaction Component” (although I'm still not sure, what the unicorn is for!) Here's a short tutorial on how to create a “Reaction Component” – both with *and* without JavaScipt.

Let's start with the CSS version!

---

## Markup

We're going to wrap each *reaction* in a `<label>`, and add the `<svg>` and an empty `<span>` after a `<input type="checkbox">`:
 
```html
<label aria-label="React with heart">
  <input type="checkbox" name="reaction-heart" value="75" style="--c:75" />
  <svg></svg>
  <span></span>
</label>
```

The `<input type="checkbox">` is what we'll use to control both `state` **and** `value`. 

---

## Icon

On dev.to, two different icons are used, when you “react” to something. For the “like“-action, there's an *unfilled* heart and a *filled* heart. Same story for the “unicorn” and “bookmark”-reactions. 

One could argue, that with slight design changes, the icons could simply toggle SVG's `fill`, `stroke` or `stroke-width` – but let's leave it at two icons. We'll `<g>`roup them within a single SVG:

```xml
<svg viewBox="0 0 24 24">
  <g><path d="M21.179 12.794l.013.014L12 22l-9.192-9.192.013-.014A6.5 6.5 0 0112 3.64a6.5 6.5 0 019.179 9.154zM4.575 5.383a4.5 4.5 0 000 6.364L12 19.172l7.425-7.425a4.5 4.5 0 10-6.364-6.364L8.818 9.626 7.404 8.21l3.162-3.162a4.5 4.5 0 00-5.99.334l-.001.001z"></path></g>
  <g><path d="M2.821 12.794a6.5 6.5 0 017.413-10.24h-.002L5.99 6.798l1.414 1.414 4.242-4.242a6.5 6.5 0 019.193 9.192L12 22l-9.192-9.192.013-.014z"></path></g>
</svg>
```

In CSS, we an use the `:checked` pseudo-selector to toggle between the two icons (in `<g>`-tags):

```css
[name*="reaction-"]:checked + svg g:first-of-type,
[name*="reaction-"]:not(:checked) + svg g:last-of-type {
  opacity: 0;
}
[name*="reaction-"]:checked + svg g:last-of-type {
  opacity: 1;
}
```

Cool, now we can toggle between the two icons using the checkbox, let's add a counter! Did you notice the `style="--c:75"` in the markup?

We'll use that for a CSS counter:

```css
counter-reset: reaction var(--c);
```

Unfortunately, we can't use the `value`-attribute, as in:

```css
counter-reset: reaction attr(value);
```
– so we have to use that extra custom property, `--c`, for the initial value.

Then, we'll hook into the `:checked`-selector again:

```css
[name*="reaction-"]:checked {
  counter-increment: reaction;
}
```

And that empty `<span>` in the markup will now play it's part:

```css
span::after {
  content: counter(reaction);
}
```

But why the **empty** `<span>`? That's because we have to add the counter as pseudo-element content (`::before` or `::after`).

Unfortunately, we can't add a pseudo-element to the `<input type="checkbox">`, as `<input>`-tags are part of the group of tags, that can't have children (aka “self-closing” tags) or pseudo-content (actually they *can* in Chrome and Safari, but it's not part of the spec!).

The rest is just bits of styling. Here's the CSS-only example on Codepen:

{% codepen https://codepen.io/stoumann/pen/JjNWbbe %}

---

## JavaScript

Even though the CSS-only version is cool, it's not very practical. You'll probably want to *store* the reaction somewhere!

Let's remove the counter-related stuff from the CSS, and the `style="--c"`-part from the markup. We'll wrap the reactions in a `<form id="react">`, and listen for changes using the `onchange`-eventListener:


```js
react.addEventListener('change', (e) => {
  const t = e.target;
  t.parentNode.lastElementChild.innerText = t.value = t.value - 0 + (t.checked ? 1 : -1);
});
```

This small snippet will *add* or *subtract* `1`(one) from the `value` of the current reaction, then set the `innerText` of the `<span>` to that.

It's within this snippet, you can add a `fetch()` (with `POST`) to store the current reaction.

On dev.to, for instance, a small `JSON`-object is `POST`ed:

```json
{
  result: "create",
  category: "like"
}
```

*Example, using JavaScript:*
{% codepen https://codepen.io/stoumann/pen/qBmRLap %}

If you want to set the text of all the `<span>`-elements to the `value` of the `<input>`s, use this small snippet to iterate the `elements`-collection of the `<form>`:

```js
[...react.elements].forEach(t => t.parentNode.lastElementChild.innerText = t.value);
```

---

That's it! Recently, there was a “Star Rating”-challenge here on dev.to (my entries were [Star Rating Using a Single Input](https://dev.to/madsstoumann/star-rating-using-a-single-input-i0l) and [Mood Selector](https://dev.to/madsstoumann/mood-selector-jhi)). 

It's always interesting to see how other developers solve problems, so please share a link in the comments, if you modify my example, or – event better – make your own “Reaction Component”!