When to use Writeable Selectors in RecoilJS
Tags: react, redux, javascript, tutorial
Published: 2020-08-19 14:29:54

A while back, I was leafing through the RecoilJS documentation trying to get a feel for the library, and I came upon a section titled:

> [Writeable Selectors](https://recoiljs.org/docs/api-reference/core/selector#writeable-selectors)

"**Weird**," I thought. Aren't selectors there to **read** data that can be computed from state? 

Why would I ever want to use a selector to update state?

Turns out the selector is a pretty great tool for mutating state! This is for two main reasons: It lives outside your view component, which gives us the benefit of seprating our *model* and *view* domains, and it can do everything else that a selector can - i.e., it can directly access whatever state objects you need for the desired mutation.

### Back to Basics - Managing State with RecoilJS

Let's take a step back and look at the basics of how state is managed with RecoilJS.

To get started, I recommend creating a sample app, installing RecoilJS, and then wrapping your app in RecoilRoot - all of this is covered in the RecoilJS [Getting Started](https://recoiljs.org/docs/introduction/getting-started) guide.

____
_Less familiar with Recoil selectors? Check out my [quick refresher](https://egghead.io/lessons/react-update-recoil-state-in-react-using-userecoilstate) on Egghead_
___

First, we have **atoms** - these are our basic state objects. 

If I were to write a simple restaurant point of sale application, I might express one table's order using an `order` atom:

```jsx
const order = atom({
  key: 'order',
  default: ['garlic bread'],   // on the house!
});
```

Then, in my React component, I would **consume** the atom using the appropriate RecoilJS action hook™️ for consuming data:

```jsx
import { useRecoilValue } from 'recoil';

export default function OrderDisplay() {
    const myOrder = useRecoilValue(order);

    return (
        <div>
            <h3>Current Order:</h3>
            {myOrder.map((food, i) => {
                return <div key={i}>{food}</div>
            })}
        </div>
    )
}
```

Notice that I can't **consume atoms directly** - I need to use one of the RecoilJS hooks, such as `useRecoilValue`. Choosing the correct hook gives me finer-grained control over how I subscribe to the state object, in terms of its effect on re-rendering.

In this case, because I'm only listening to data and not writing it, I'm better off using `useRecoilValue` than either `useSetRecoilState` or the generic `useRecoilState` .

Now, say the customer wants to order a hamburger - I'll need to push additional data to the state object.

## Updating State - useState Style

In plain old React, updating state is as simple as calling the **setter** exported by `useState`.

```jsx
const [order, setOrder] = useState([]);

const addMenuItem = (food) => setOrder([...order, food])
```

And I can use the same approach with **Recoil's** analogous hook, `useRecoilState`:

```jsx
const [order, setOrder] = useRecoilState(order);

const addMenuItem = (food) => setOrder([...order, food])
```
But what if my state mutation gets a little more complicated?

That's where reducers come in.

## Updating State - useState + Reducer

Suppose the customer changed her mind and decided to cancel the hamburger?

To handle this slightly more complicated state update, I can create an immutable reducer function that takes the previous state, the change that I want to implement, and returns a new state object. Then, as before, I'll call the setter to update the state.


```jsx
import { useRecoilValue } from 'recoil';

export default function OrderDisplay() {
    const [myOrder, setMyOrder] = useRecoilState(order);

    // reducer function
    const removeFoodItem = (currentOrder, foodToRemove) => {
        const foodToRemoveIndex = currentOrder.findIndex((val => val === foodToRemove));

        return [...currentOrder.slice(0, foodToRemoveIndex), ...currentOrder.slice(foodToRemoveIndex + 1)];
    }

    const onRemoveFood = (food) => () => {
        const newOrder = removeFoodItem(myOrder, food);
        setMyOrder(newOrder);
    }

    return (
        <div>
            <h3>Current Order:</h3>
            {myOrder.map((food, i) => {
                return (
                    <div key={i}>{food}
                        <span onClick={removeFoodItem(food)}>[x]</span>
                    </div>)
            })}
        </div>
    )
}
```

 

The thing is, now that I've defined a separate function to handle the update logic, it becomes clear that the function doesn't really belong in this view component. It's just kind of noise. 

So let's extract it out:

```jsx
const removeFoodItem = (currentOrder, foodToRemove) => {
    const foodToRemoveIndex = currentOrder.findIndex((val => val === foodToRemove));

    return [...currentOrder.slice(0, foodToRemoveIndex), ...currentOrder.slice(foodToRemoveIndex + 1)];
}
```

```jsx
import { useRecoilValue } from 'recoil';

export default function OrderDisplay() {
    const [myOrder, setMyOrder] = useRecoilState(order);

    const onRemoveFood = (food) => () => {
        const newOrder = removeFoodItem(myOrder, food);
        setMyOrder(newOrder);
    }

    return (
        <div>
            <h3>Current Order:</h3>
            {myOrder.map((food, i) => {
                return (
                    <div key={i}>{food}
                        <span onClick={removeFoodItem(food)}>[x]</span>
                    </div>)
            })}
        </div>
    )
}
```

Much cleaner :) 

Except that we need to explicitly feed this method the `currentOrder` from our React component, because a reducer is simply an immutable function. Let's see if we can make this even cleaner.

### Using Selectors to Update Data

Let's start by looking at the regular selector. I'll define a selector that will return the total price for any order by cross-checking the food menu with a **price list:**

```jsx
const priceList = [
    { name: 'cappucino', price: 5 },
    { name: 'latte', price: 7 },
    { name: 'espresso', price: 1.5 },
    { name: 'coffee', price: 4 },
    { name: 'cheesecake', price: 4 },
]

const order = atom({
    key: 'order',
    default: [],
});

const orderInfo = selector({
    key: 'orderTotalPrice',
    get: ({ get }) => {
        return get(order)
            .map(food => {
                const foodFromList = priceList.find(i => i.name === food)
                return foodFromList ? foodFromList.price : 0;
            })
            .reduce((sum, current) => sum + current, 0
      }
});
```

The selector defines a **getter** method that itself takes a `get` object as an argument and uses it to extract the `order` state object in order to cross-check it with our price-list, and then sum up the prices. It can be consumed just like we consume an atom - with the appropriate RecoilJS action hook™️ .

## Updating State - Writeable Selectors

Now how about writeable selectors? A writeable selector is a selector that exposes a **setter** method in addition to a getter (yes, a selector can expose both).

We'll use a writeable selector to add food items to our menu (like what we did before using `useState`): 

```jsx
const addFood = selector({
    key: 'addFood',
    set: ({ set, get }, newFood) => {
        set(order, [...get(order), newFood])
    },
});
```

And now we would use the `useSetRecoilState` hook to consume this selector, and just call it with our new food of choice, *à la*:

```jsx
const setAddFood = useSetRecoilState(addFood);

...
<div onClick={() => setAddFood(food)}>{food}</div>
```

Rather than putting our state modification logic in the view component, or using an immutable reducer method, we've declared a single method that can do two things:

1. Access the data from our store, **and**
2. Update the data exactly how we want

The elegance of this ***really*** shines when we apply it to the slightly-more-complicated removal of food items:

```jsx
const removeFoodSelector = selector({
    key: 'removeFoodSelector',
    set: ({ set, get }, foodToRemove) => {
        const currentOrder = get(order);
        const foodToRemoveIndex = currentOrder.findIndex((val => val === foodToRemove));
        set([...currentOrder.slice(0, foodToRemoveIndex), ...currentOrder.slice(foodToRemoveIndex + 1)]);
    },
});
```

Look familiar? Yep, we've just combined the reducer logic with the ability to directly access our state object.

And now, finally, we can really clean up our view component:

```jsx
import { useRecoilValue } from 'recoil';

export default function OrderDisplay() {
    const removeFoodItem = useSetRecoilState(removeFoodSelector);

    return (
        <div>
            <h3>Current Order:</h3>
            {myOrder.map((food, i) => {
                return (
                    <div key={i}>{food}
                        <span onClick={() => removeFoodItem(food)}>[x]</span>
                    </div>)
            })}
        </div>
    )
}
```

Notice how the final product abstracts away all state updating logic to where it belongs, i.e. where the model is defined and where it can access any other data that it requires without it having to accept it as a parameter from the view layer.

I'm not saying that writeable selectors are necessarily the best way to define state updating logic. How you architect your state management depends very much on system requirements and personal preference. But it is a compelling a choice.