DEV Community

Atle Frenvik Sveen
Atle Frenvik Sveen

Posted on

Recoil.js

Redux is pretty awesome. Well, the idea behind redux is pretty awesome (go ahead and read Human Redux, it's great!). But the implementation, and React-integration, leads to lots of files, biolerplate and hassle [personal opinion, feel free to disagree].

So, when something new1 comes along that promises to solve those issues, it has to be explored. And so I did, a year ago or something. Recoil.js brands itself as “A state management library for React” that is “Minimal and Reactish”.

Sounds good? I was sceptical, but after using it for about a year I very much like what I see. My main gripe with it is perhaps a certain lack of excamples, so to alleviate that some examples might be what’s called for.

So, why recoil?

Well, for a starter, consider this component

import React, {useState} from 'react';

export const MyCounter = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      <div>Count: {count}</div>
      <button onClick={() => setCount((count) => count + 1)}>Increment</button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

A bog-standard, boring, counter.

But, now we want to refactor this and split it into a button-component and a display component

import React, {useState} from 'react';

interface MyCounterButtonProps {
  increment: () => void;
}
const MyCounterButton = ({increment}: MyCounterButtonProps) => 
  <button onClick={() => increment()}>Increment</button>;

interface MyCounterDisplayProps {
  value: number;
}
const MyCounterDisplay = ({value}: MyCounterDisplayProps) => 
  <div>Count: {value}</div>;

export const MyCounter = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      <MyCounterDisplay value={count} />
      <MyCounterButton increment={() => setCount((count) => count + 1)} />
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Well, this works. But we have to pass props, and these components are coupled. Surely there is a better way?

This is where recoil comes into play, if we use an atom:

import React from 'react';
import {atom, useRecoilValue, useSetRecoilState} from 'recoil';

const counterState = atom<number>({
  key: 'counterState',
  default: 0,
});

const MyCounterButton = () => {
  const setCounter = useSetRecoilState(counterState);
  return <button onClick={() => setCounter((count) => count + 1)}>Increment</button>;
};

const MyCounterDisplay = () => {
  const value = useRecoilValue(counterState);
  return <div>Count: {value}</div>;
};

export const MyCounter = () => (
  <div>
    <MyCounterDisplay />
    <MyCounterButton />
  </div>
);
Enter fullscreen mode Exit fullscreen mode

See how we moved the state from the MyCounter component? Instead we put it into an atom, which we can access using the useRecoilValue hook, and set using the useSetRecoilState hook.

This allows us to share state between components without passing it as props. Big deal? Well, not in this case perhaps, but we avoid long prop chains, and we are able to share state between components that are far apart from each other in the component tree.

But, wait, is recoil just useState on streoids?

No, by no means! Recoil is a fully-fledged state managemnent library, with support for async queries and reducers. But we have to start somewhere!

This post is already getting long though, so we'll just cover one (simple) example of derived state.

Say we wanted our counter to display a message when the count was an even number.

Easy:

const IsEvenDisplay = () => {
  const value = useRecoilValue(counterState);
  return <div>{value % 2 == 0 ? "even!": null}</div>;
}
Enter fullscreen mode Exit fullscreen mode

This is of course really trivial, but say we had a more complex computation, that was unnatural to compute in a component.

Then we could use a recoil selector, and compute it there:

const isCounterEvenState = selector<boolean>({
  key: "isCounterEvenState",
  get: ({get}) => get(counterState) % 2 === 0
});

const IsEvenDisplay = () => {
  const isCounterEven = useRecoilValue(isCounterEvenState);
  return <div>{isCounterEven ? "even!": null}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Clean, eh?

And of course, there are lots of other aspects to recoil. How do we handle async queries? How do we test this? How do we organize things?

Those are all interesting subjects that we'll touch upon in a later installment.

For now: If you haven't played around with recoil I strongly recommend that you do. The examples shown here are available at the first, half-decent, codepen-lookalike I could find: here.

Installments in this series:


  1. "New" is a very relative term.  

Top comments (0)