Bytes #48 - the cutthroat Pâtisserie world

the cutthroat Pâtisserie world

Issue #48.May 17, 2021.2 Minute read.

Angular 12 goes all-in on Ivy

Ivy

Enterprise apps clinging to whatever innovation they can get

There's a lot to unpack from last week's Angular 12 release, the biggest being the framework's new "Ivy-everywhere" approach -- a phrase which conjures some unpleasant (and very itchy) memories of hikes at Boy Scout camp that went terribly wrong.

Wtf is Ivy? It's Angular's default rendering & compilation pipeline that has slowly been replacing View Engine (the legacy pipeline) over the last year.

Ivy has better code completions, error reporting, hints, and notifications inside Angular templates. Deprecating View Engine should make Angular simpler, faster, and easier to use & maintain.

What else is new in v12:

  • RIP Protractor -- The Angular team "has been working with the community to determine the future of Protractor." AKA we're sick of working on it and hope someone in the community will take it over.

  • RIP IE 11 -- Angular 12 joins the other cool kids like Vue 3 who are deprecating support for IE 11. v12 will include IE11 deprecation warnings and v13 will officially remove support.

  • Nullish Coalescing support -- You can now use JavaScript's "Nullish Coalescing" (??) feature inside Angular templates.

There's also new Webpack 5 support, inline Sass, and inline critical CSS.

The Bottom Line

It's easy for ~~us~~ some people to make jokes about Angular being the JavaScript framework with the strongest Boomer energy.

But then they come out with this big v12 release, they brag about their npm downloads growing from 1.6m in 2019 to 2.4m in 2020, RxJS releases a big update, and it seems like Angular's back on the come-up.


Cool Bits

  1. Babel wrote about how it's running out of money. Naturally the reactions to this were only positive since we're a kind, supportive, and appreciative community 🥲.

  2. Stephanie Eckles wrote a primer on CSS Container Queries that will help you feel smarter than your co-workers since this is a new, experimental feature.

  3. Google Docs is switching from HTML-based rendering to Canvas-based rendering, but they aren't the first ones to do so.

  4. In this article, Ben Illegbodu shows you how to lower your reliance on Lodash with 9 single-statement JS algorithms for common data transformations.

  5. Deno 1.1 was released. Ah, that explains why why nobody was talking about the Babel situation at all last week -- everyone was too busy trying out Deno's new built-in test runner.

  6. Prettier 2.3 came out last week, and it fixes some longstanding issues with the JavaScript printer. JavaScript Pam Beasley is very pleased.

  7. Piero made a URL lengthener, which is a concept that's almost as cursed as most of Elon Musk's SNL sketches.

  8. Sandro wrote a great guide called Modern JavaScript: Everything you've missed over the last 10 years. It's the perfect resource if you learned JavaScript in 2011, but then gave it all up to pursue your dream of becoming a classically trained French pastry chef, only to discover years later that the cutthroat Pâtisserie world had left you feeling jaded and alone, and so you just decided last week to get back into web development, but really wished there was a concise guide that could help fill in all the gaps of what you'd missed in JavaScript over the last decade while you were busy perfecting your pâte à choux technique. It's also a good article if you're just curious what's changed in JavaScript over the last 10 years.


A (probably too long for this Newsletter) Introduction to Recoil

Created from a team at Facebook, Recoil is an "experimental set of utilities for state management in React".

It should be noted that even though Recoil was created by a team at Facebook (as with React), it's not "in any way, an 'official' state management library for React" 🙃

Per usual, before we dive into the code let's look at why Recoil was created and what problems it attempts to solve.

There's an internal tool at Facebook called "Comparison View". It's a "data analysis app for performance data". That's a fancy way of saying it's an app that lets you analyze performance data across Facebook. As you can imagine, it's rather complex. What they found was that for their use cases, both React component state and Redux didn't work well. They kept hitting performance and flexibility limits. I think it's safe to assume this wasn't just a "they don't know what they're doing" thing.

Recoil was created to solve these problems. Specifically, flexible shared state and derived data and queries.

Flexible shared state

Performant, synchronized, shared state across any React component that needs it in your component tree.

Derived data and queries

The ability to (re)compute values (performantly and efficiently) whenever any of the state in your application changes.


What I like about Recoil is its simplicity. Whenever I'd work with Redux, the mental model always felt completely different than React's. Almost like there was context switching costs moving between "React code" and "Redux code". With Recoil, though not completely aligned, it's much closer.

We're going to build the most pointless app to demonstrate the benefits of a state management library, a counter app. However, it's a nice warmup to the syntax.

atoms

atoms are changeable, subscribable, units of state. Any React component that subscribes to an atom will receive the new, updated value whenever the atom changes. Whenever an atom changes, only the React components that subscribe to that atom will be updated.

You create a new atom by invoking atom (which you can get as a named import on the recoil package), passing it an object with a unique key property and its default value.

import { atom } from 'recoil'

const count = atom({
  key: 'countState',
  default: 0
})

Now, any component that needs access to the count's state can subscribe to it and update it. To do that, you use the useRecoilState Hook. It has the same API as React.useState except instead of passing it a default value, you pass it the atom.

import React from 'react'
import { atom, useRecoilState } from 'recoil'

const countState = atom({
  key: 'countState',
  default: 0
})

function Counter() {
  const [count, setCount] = useRecoilState(countState)

  const increment = () =>
    setCount((count) => count + 1)

  const decrement = () =>
    setCount((count) => count - 1)

  return (
    <React.Fragment>
      <button onClick={decrement}>-</button>
      <h1>{count}</h1>
      <button onClick={increment}>+</button>
    </React.Fragment>
  )
}

Like most React libraries, we need to wrap our app with a provider component so Recoil can use context to pass state anywhere in our component tree. For Recoil, this provider component is named RecoilRoot.

import React from 'react'
import {
  RecoilRoot,
  atom,
  useRecoilState
} from 'recoil'

const countAtom = atom({
  key: 'countState',
  default: 0
})

function Counter() {
  const [count, setCount] = useRecoilState(countAtom)

  const increment = () =>
    setCount((count) => count + 1)

  const decrement = () =>
    setCount((count) => count - 1)

  return (
    <React.Fragment>
      <button onClick={decrement}>-</button>
      <h1>{count}</h1>
      <button onClick={increment}>+</button>
    </React.Fragment>
  )
}

export default function App () {
  return (
    <RecoilRoot>
      <Counter />
    </RecoilRoot>
  )
}

💻 Play with the code.

At this point we know the syntax for the absolute basics of Recoil – creating an atom and then subscribing and updating it from inside of a component.

If you'll remember back to the goals of Recoil, they were "flexible shared state and derived data and queries." We now know how to add "flexible shared state" to our application using atoms. Next we need to figure out "derived data and queries".

selectors

One of the core foundations of functional programming is the concept of pure functions. Pure functions maximize the predictability of your program. A function is "pure" if it meets the following criteria:

  1. Always return the same result given the same arguments.
  2. Its execution doesn't depend on the state of the application.
  3. Doesn't modify the variables outside of its own scope.

When you call a function that is "pure", you can predict exactly what's going to happen based on its input.

The way Recoil solves "derived data and queries" is by leveraging pure functions which can derive a new value from existing atoms' state. The term for how they do this is called a "selector".

"A selector represents a piece of derived state. You can think of derived state as the output of passing state to a pure function that modifies the given state in some way."

Again, to get a soft introduction to the syntax, we'll use a contrived example. Let's say we wanted to, using our previous count state, derive whatever count * 2 is. We can do so by creating a double selector.

import { selector } from 'recoil'

const double = selector({
  key: 'double',
  get: ({ get }) => {
    const count = get(countAtom)

    return count * 2
  }
})

Notice that we pass selector an object with two properties, a unique key and a get method. get is a function that recoil will invoke passing it an object that has another get method on it. This get method allows you to get access to an atom's (or another selector's) piece of state. We can then take that state (in our case, count), and derive a new value from it.

Instead of using the useRecoilState Hook (which gives you both the value and a way to update the value), since selectors are derived values only, you use the useRecoilValue Hook instead.

import React from 'react'
import {
  RecoilRoot,
  atom,
  useRecoilState,
  selector,
  useRecoilValue
} from 'recoil'

const countAtom = atom({
  key: 'countState',
  default: 0
})

const double = selector({
  key: 'double',
  get: ({ get }) => {
    const count = get(countAtom)

    return count * 2
  }
})

function Counter() {
  const [count, setCount] = useRecoilState(countAtom)
  const doubled = useRecoilValue(double)

  const increment = () =>
    setCount((count) => count + 1)

  const decrement = () =>
    setCount((count) => count - 1)

  return (
    <RecoilRoot>
      <button onClick={decrement}>-</button>
      <h1>{count} - {doubled}</h1>
      <button onClick={increment}>+</button>
    </RecoilRoot>
  )
}

export default function App () {
  return (
    <RecoilRoot>
      <Counter />
    </RecoilRoot>
  )
}

💻 Play with the code.

What I love about selectors is you get rid of the ceremony around syncing state. Instead, you can, in a very predictable manner, abstract that complexity to its own selector. Even better, the selector will only recompute whenever one of the values it depends on changes. This also means that any React components that depend on a particular selector will only re-render if one of the values the selector depends on changes as well.

Async State

Our counter example has proven effective in giving us a solid understanding of the Recoil syntax, however, it comes up short when it comes to practicality.

One fundamental aspect of building any application is the ability to fetch asynchronous data. Here's another thing that's rad about selectors – they can be asynchronous. The mental model still works the same. Instead of deriving values locally, you have the ability to derive values asynchronously from a server. Under the hood, Recoil still models this as a pure function. If the selector has the same inputs, it will always give you the same outputs (by utilizing cache).

To do this, you just return a Promise from the selectors get method which resolves with the value.

import { atom, selector } from 'recoil'

export const language = atom({
  key: 'language',
  default: 'javascript'
})

export const reposQuery = selector({
  key: 'reposQuery',
  get: async ({ get }) => {
    const repos = await getRepos(get(language))

    return repos
  }
})

No thunks, no sagas, you just create async selectors then use them whenever you want using useRecoilValue.


Should you use Recoil?

Naturally, it depends. Here's the mental checklist I'll go through from now on when deciding between component state, context, Redux, and Recoil.

  1. Do I need shared state? If no, use component state. If yes, continue.
  2. Can I move the shared state up to the closest parent component without creating performance or prop drilling issues? If yes, do that. If no, continue.
  3. Is my shared state simple and predictable enough where I can put it on context? If yes, do that. If no, continue.
  4. Use Recoil.

What I never liked about Redux is it felt like it was all or nothing, Redux vs React. There's so much ceremony in setting up the store, actions, reducers, etc. that by the time you were done, you might as well just put everything in Redux regardless of best practices. With Recoil, it's the opposite. You can simply abstract shared state amongst different atoms/selectors and then sprinkle them in while your React code stays almost exactly the same.

As someone who has always been a Redux sympathizer ("it's great if you use it for the right problem" was my mantra), I didn't expect to love Recoil as much as I do. I also didn't expect to throw out Redux completely, but here we are.

Join Bytes

Delivered to 105,434 developers every Monday