Recoil to Jotai (with Typescript)

Tags: recoil, jotai, typescript, react
Published: 2020-10-28 16:42:17


Our consulting team has enjoyed using several excellent [react libraries](https://github.com/pmndrs) such as [react-spring](https://github.com/pmndrs/react-spring), [react-three-fiber](https://github.com/pmndrs/react-three-fiber), [react-three-flex](https://github.com/pmndrs/react-three-flex) lately. As a result, we were intrigued when [Poimandres'](https://github.com/pmndrs) [announced](https://twitter.com/0xca0a/status/1303354125246255104?lang=en) [Jotai](https://github.com/pmndrs/jotai), a [Recoil](https://recoiljs.org/) state management alternative. Couple this with the fact we have been using more and more [TypeScript](https://www.typescriptlang.org/), we thought it might be interesting to explore the differences between a _Recoil_ project and one implemented in _Jotai_ with respect to explicit typing. 

In an attempt to approximate an 'apples to apples' comparison, we decided on [Jaques Bloms'](https://twitter.com/jacques_codes) [recoil-todo-list](https://github.com/jacques-blom/recoil-todo-list/tree/completed) as a starting point. It not only uses Typescript, but also utilizes a number of [Recoil](https://recoiljs.org/docs/introduction/core-concepts) idioms like [Atoms](https://recoiljs.org/docs/introduction/core-concepts#atoms), [Selectors](https://recoiljs.org/docs/introduction/core-concepts#selectors) and [AtomFamily](https://recoiljs.org/docs/api-reference/utils/atomFamily/)

Below are some highlights of the [recoil-todo-list](https://github.com/c0d3t3k/recoil-todo-list/tree/jotai) conversion. These steps attempt to illustrate some of the syntatic/algorithmic differences between the two libraries. So let's dive in!

Similar to Recoil, Jotai uses a [context provider](https://reactjs.org/docs/context.html#contextprovider) to enable app wide access to state. After [installing Jotai](https://www.npmjs.com/package/jotai) just needed to modify the `index.tsx` from Recoil's `<RecoilRoot>` to Jotai's `<Provider>`. 


```tsx
// index.tsx

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { Provider } from 'jotai'
//import {RecoilRoot} from 'recoil'

ReactDOM.render(
    <React.StrictMode>
        {/* <RecoilRoot> */}
        <Provider>
            <App />
        </Provider>
        {/* </RecoilRoot> */}
    </React.StrictMode>,
    document.getElementById('root'),
)
```

The snippet below implements the app's obligatory [Dark Mode](https://en.wikipedia.org/wiki/Light-on-dark_color_scheme) state management. In `Header.tsx` just need a small syntatic change to [Jotai](https://github.com/pmndrs/jotai#usetheatom)'s `{atom, useAtom}` from [Recoil](https://recoiljs.org/docs/introduction/core-concepts#atoms)'s `{atom, useRecoilState}`. 

```tsx
// Header.tsx except
...

export const Header: React.FC = () => {
    // RECOIL //
    //const [darkMode, setDarkMode] = useRecoilState(darkModeState)

    // JOTAI //
    const [darkMode, setDarkMode] = useAtom(darkModeState)

...
```

Next, we needed to convert `Tasks.tsx`. We chose to go with an Task interface in order to custom defined type a `TasksAtom` that will be used to store the Task indexes.

```tsx
// Tasks.tsx excerpt

...
// RECOIL //
// export const tasksState = atom<number[]>({
//     key: 'tasks',
//     default: [],
// })

// export const tasksState = atom([] as number[])

// JOTAI //
export interface Task {
    label: string,
    complete: boolean
}

export const tasksAtom = atom<number[]>([])

export const Tasks: React.FC = () => {
    const [tasks] = useAtom(tasksAtom)
...

```

Then we converted [`Task.tsx`](https://github.com/c0d3t3k/recoil-todo-list/blob/jotai/src/components/Tasks.tsx), using a Jotai `util` implementation similar to Recoil's [`atomFamily`](https://recoiljs.org/docs/api-reference/utils/atomFamily/). Notice here that Jotai's implementation of [`atomFamily`](https://github.com/pmndrs/jotai/blob/master/docs/utils.md#atomfamily) includes an explicit definition of a getter and setter which internally utilizes the `tasksAtom` defined in [`Tasks.tsx`](https://github.com/c0d3t3k/recoil-todo-list/blob/jotai/src/components/Tasks.tsx). 

Btw, [Jotai Pull Request #45](https://github.com/pmndrs/jotai/pull/45) went a long way in helping us understand how this should work (props to [@dai-shi](https://github.com/dai-shi) and [@brookslybrand](https://github.com/brookslybrand))


```tsx
// Task.tsx excerpt
...

// RECOIL //
// export const taskState = atomFamily({
//     key: 'task',
//     default: {
//         label: '',
//         complete: false,
//     },
// })

// JOTAI //
// https://github.com/pmndrs/jotai/pull/45
export const taskState = atomFamily(
    (id: number) => ({
        label: '',
        complete: false,
    } as ITask)
)


export const Task: React.FC<{id: number}> = ({id}) => {
    //const [{complete, label}, setTask] = useRecoilState(taskState(id))
    const [{complete, label}, setTask] = useAtom(taskState(id))
...
```
The next file to convert is [`Input.tsx`](https://github.com/c0d3t3k/recoil-todo-list/blob/jotai/src/components/Input.tsx). We chose to substitute the Recoil [useRecoilCallback](https://recoiljs.org/docs/api-reference/core/useRecoilCallback) with Jotai's [useAtomCallback]().

```tsx
// Input.tsx excerpt

...
    // RECOIL
    // const insertTask = useRecoilCallback(({set}) => {
        //     return (label: string) => {
        //         const newTaskId = tasks.length
        //         set(tasksState, [...tasks, newTaskId])
        //         set(taskState(newTaskId), {
        //             label: label,
        //             complete: false,
        //         })
        //     }
        // })

    // JOTAI //
    const insertTask = useAtomCallback(useCallback((
        get, set, label: string
    ) => {
        const newTaskId = tasks.length
        set(tasksAtom, [...tasks, newTaskId])
        set(taskState(newTaskId), {
            label: label,
            complete: false,
        })
    }, [tasks]));
...

```

Finally, in `Stats.tsx`, we replaced the [Recoil Selectors](https://recoiljs.org/docs/api-reference/core/selector/) with readonly Jotai Atoms using computed Task state. In this case, there appears to be only a slight syntatic difference, mostly around the use of string reference keys.

```tsx
// Stats.tsx excerpt
...

// RECOIL //
/*
const tasksCompleteState = selector({
    key: 'tasksComplete',
    get: ({get}) => {
        const taskIds = get(tasksState)
        const tasks = taskIds.map((id) => {
            return get(taskState(id))
        })
        return tasks.filter((task) => task.complete).length
    },
})

const tasksRemainingState = selector({
    key: 'tasksRemaining',
    get: ({get}) => {
        const taskIds = get(tasksState)
        const tasks = taskIds.map((id) => {
            return get(taskState(id))
        })
        return tasks.filter((task) => !task.complete).length
    },
})
*/

// JOTAI
const tasksCompleteState = atom(
    get => {
        const tasksState = get(tasksAtom)
        const tasks = tasksState.map((val, id) => {
            return get(taskState(id))
        })
        return tasks.filter((task: Task) => task.complete).length
    },

)
const tasksRemainingState = atom(
    get => {
        const tasksState = get(tasksAtom)
        const tasks = tasksState.map((val, id) => {
            return get(taskState(id))
        })
        return tasks.filter((task: Task) => !task.complete).length
    }
  )
...

```

## Final Thoughts:

- Overall, we were impressed by how things "just worked".
- The syntactic differences were easy to navigate as well as the different mechanisms for referencing atoms.
- With the relative lack of documentation currently available, we recommend reviewing [Jotai issues and pull requests](https://github.com/pmndrs/jotai/issues?q=) to get more familiar with the concepts and techniques.
- We enjoyed this exercise and as a result will be doing more investigation into using Jotai in our production solutions.

[Github Source](https://github.com/c0d3t3k/recoil-todo-list/tree/jotai) and [CodeSandbox](https://codesandbox.io/s/recoil-to-jotai-s9jm5) are also available.

{% codesandbox recoil-to-jotai-s9jm5 %}