Nuxtstop

For all things nuxt.js

Advanced Large Object Management with jotai

Advanced Large Object Management with jotai
7 0

Getting Started

This article assumes the following knowledge:

  • Basic understanding of jotai
  • You have seen the Large Object concept 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 (jotai/optic-ts integration) - create a read-write derived atom based on a predicate
  • splitAtom (jotai utility) - transform a data array into an array of atoms
  • selectAtom (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?

{
 "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 }
     ]
   }
 ]
}
Enter fullscreen mode Exit fullscreen mode

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 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.

const useAttributeAtom = ({ attributesAtom, index }) => {
 return useMemo(() => {
   return focusAtom(attributesAtom, (optic) => optic.at(index));
 }, [attributesAtom, index]);
};
Enter fullscreen mode Exit fullscreen mode
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>
 );
};
Enter fullscreen mode Exit fullscreen mode

See the full code example

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!