Advanced Large Object Management with jotai
Tags: jotai, react, state, object
Published: 2021-09-26 21:10:11


## Getting Started

This article assumes the following knowledge:
- Basic understanding of jotai
- You have seen the [Large Object concept](https://docs.pmnd.rs/jotai/advanced-recipes/large-objects) on the official jotai documentation

### Goal
- Understand how to focus on deeply nested parts of a large object
- Not repeat what jotai library already explains

#### TLDR;
full code example: 

https://codesandbox.io/s/pensive-fast-89dgy?file=/src/App.js

## Introduction

Managing large objects is sometimes necessary when dealing with structured client state. For eg. managing a very sophisticated tree or maybe a content editor. Jotai makes this very simple with its various utilities and plugins to manage state.

Utilities and tools that will be discussed in this article:
- [focusAtom](https://docs.pmnd.rs/jotai/integrations/optics) (jotai/optic-ts integration) - create a read-write derived atom based on a predicate
- [splitAtom](https://docs.pmnd.rs/jotai/api/utils#split-atom) (jotai utility) - transform a data array into an array of atoms
- [selectAtom](https://docs.pmnd.rs/jotai/api/utils#select-atom) (jotai utility) - create a read-only atom based on a predicate

Challenges with managing large objects with only react
- Managing updates to specific parts that are deeply nested and reporting the change back to the main object
- Managing deeply nested callbacks and also changing the data manually with setters, spreading, you name it!

None of the challenges listed above are inherently bad but when a developer does repetitive work, the thought always comes to mind about how all this boilerplate can be abstracted away.

Luckily jotai has a nice solution to these challenges.

Let’s talk about doing this the jotai way.

For this article we will be managing a cat! Yes...meow! The application will tell a Vet if specific body parts of the cat are injured.

Please note the object shape below is not necessarily how you would do this in a real application but designed to give a good example for the purposes of this article

Now what will our cat injury data look like? 

```json
{
 "name": "Sparkles",
 "owner": { "id": 1, "firstName": "John", "lastName": "Doe" },
 "parts": [
   {
     "type": "legs",
     "attributes": [
       { "placement": "front-left", "injured": false },
       { "placement": "front-right", "injured": false },
       { "placement": "back-left", "injured": false },
       { "placement": "back-right", "injured": true }
     ]
   },
   {
     "type": "tail",
     "attributes": [{ "injured": true }]
   },
   {
     "type": "ears",
     "attributes": [
       { "placement": "left", "injured": false },
       { "placement": "right", "injured": true }
     ]
   }
 ]
}
```

First let's understand two types of state within large objects and how to access them 
- View only state 

> For state that we only need to view, we can use the selectAtom to access them. The selectAtom will give you a read-only atom that works well for this scenario.

- Editable state 

> For state that we need to edit, we can use the focusAtom to literally focus on these sections and edit them without large callbacks to merge the data back into the main object.

Jotai documentation already explains how to go at least one level deep, so the question you may ask is, how do we get to the nested arrays like the cat’s attributes and manage the data individually?

You may be tempted to split the attributes array using splitAtom, however, splitAtom only creates atoms from raw data and this data has no way of knowing how to report itself back to the root node.

So how do we update each “cat attribute” without managing the entire array ourselves?

The trick lies within the [optic-ts](https://github.com/akheron/optics-ts) integration.

You can focus on array indexes using the **at(index)** function which keeps an established reference to the root node.

See code example below.

```js
const useAttributeAtom = ({ attributesAtom, index }) => {
 return useMemo(() => {
   return focusAtom(attributesAtom, (optic) => optic.at(index));
 }, [attributesAtom, index]);
};
```

```js
const Attribute = ({ attributesAtom, index }) => {
 const attributeAtom = useAttributeAtom({ attributesAtom, index });
 const [attribute, setAttribute] = useAtom(attributeAtom);
 
 return (
   <div style={{ display: "flex" }}>
     <label>
       <span style={{ marginRight: "16px" }}>
         {attribute.placement}
       </span>
       <Switch
         onChange={(checked) =>
           setAttribute((prevAttribute) => ({
             ...prevAttribute,
             injured: checked
           }))
         }
         checked={attribute.injured}
       />
     </label>
   </div>
 );
};
```

See the [full code example](https://codesandbox.io/s/pensive-fast-89dgy)

<iframe src="https://codesandbox.io/embed/pensive-fast-89dgy?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="pensive-fast-89dgy"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

### What did we achieve?
- We were able to change focused pieces of the large object without deep prop drilling of any onChange functions
- We managed “global” state within the application while keeping the interfaces resembling React.

## Important Tips!

- The starting atom (root node) must be a writable atom. This helps when derived atoms need to write back changed information
- Atoms created within a render should be memoized or else you will have too many re-renders and most likely React will throw an error stating this exactly.

Thanks for reading!

Have you had this problem before?

Let me know if you have done this with jotai before and what solutions you came up with. 

Always looking to learn more!