Jotai, now with Optics
Tags: react, lenses, jotai, functional
Published: 2020-10-31 14:05:48

 More and more [functional paradigms](https://en.wikipedia.org/wiki/Functional_programming) have been finding their way into our contracting work. This really accelerated when we started using [React Hooks](https://reactjs.org/docs/hooks-overview.html) a while back. In fact, back in the day, we were tasked to convert a legacy [Angular](https://angular.io/) [ThreeJS](https://threejs.org/) project we had written earlier to React / [react-three-fiber]() for performance, ease of maintenance, etc. Given the increasing complexity, we wanted a more atomic, composable state management system (of course this was before [Recoil](https://recoiljs.org/) had been introduced). After some due diligence, we settled on [Grammarly's Focal](https://github.com/grammarly/focal). This library, although a bit older, is powerful and introduced us to the intriguing [FP](https://en.wikipedia.org/wiki/Functional_programming) concepts of [Optics](https://degoes.net/articles/fp-glossary#:~:text=Optics), [Lenses](https://degoes.net/articles/fp-glossary#:~:text=Lenses), etc

 Fast forward to now and we are learning more about [Jotai](https://github.com/pmndrs/jotai), a [Recoil](https://recoiljs.org/) alternative from [Poimandres](https://github.com/pmndrs) ( creators of [react-three-fiber](https://github.com/pmndrs/react-three-fiber), etc.). Needless to say, we were very excited when we stumbled upon [Jotai Issue #44](https://github.com/pmndrs/jotai/issues/44), a discussion concerning focusable atoms started by [Meris Bahtijaragic](https://github.com/merisbahti) and the compelling work that resulted, [jotai-optics](https://github.com/c0d3t3k/jotai-optics). This code wraps another library we have been very intrigued by as of late, [optics-ts](https://github.com/akheron/optics-ts) which provides a whole new level of typesafe, functional goodness.

 Now, if the concept of [Optics](https://degoes.net/articles/fp-glossary#:~:text=Optics) is new to you, there are some excellent introductions in the context of functional programming. One such concise example is [@gcanti's article](https://medium.com/@gcanti/introduction-to-optics-lenses-and-prisms-3230e73bfcfe) on lenses and prisms, and there are [plenty more](https://www.google.com/search?q=optics+introduction+functional+programming). [John DeGoes' Glossary of Functional Programming](https://degoes.net/articles/fp-glossary) will also help with any new [FP](https://en.wikipedia.org/wiki/Functional_programming) vocabulary.  However, our humble goal here is to provide more of a practical (vs academic) example.

 In order to explore this new functionality, we will use an existing Recoil example. We will not only convert to [Jotai](https://github.com/pmndrs/jotai), but also add some extra functionality to soft introduce some benefits of [`jotai-optics`](https://github.com/merisbahti/jotai-optics) (and [`optics-ts`](https://github.com/akheron/optics-ts)). 
 
 For this exercise, we thought it might be fun to upgrade [Diogo Gancalves'](https://github.com/diogo405) cool [Joeflix](https://github.com/diogo405/joeflix) app to [JotaiFlix](https://github.com/c0d3t3k/joeflix/tree/jotai-optics)!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/eyzk8f1lhz4et5fh9c6f.gif)

Let's get started.

First, we we need to replace `RecoilRoot` with the Jotai `Provider`
 
```js
// App.js exceprt
...
//import {RecoilRoot} from 'recoil'
import { Provider } from "jotai";

...

function App() {
  return (
    /* <RecoilRoot> */
    <Provider>
    <JotaiDebugger />
      <Router>
        <FeedbackPopup />
...
```

Next, we will add some Favorites and History to the UI. This will give us some specific user generated state our Optics can act upon. In order to accomplish this, we need to first create some Jotai Atoms that will store this state. While we are at it, we will include some default values. 

```js
// state.js excerpt
...
export const historyAtom = atom([
    {id: 62286, title: "Fear the Walking Dead", desc: "What did the world look like as it was transformin… the end of the world, will answer that question.", banner: "/58PON1OrnBiX6CqEHgeWKVwrCn6.jpg", type: "tv"},
    {id: 528085, title: "2067", desc: undefined, banner: "/5UkzNSOK561c2QRy2Zr4AkADzLT.jpg", type: "movie"}
])

export const favoritesAtom = atom([
    {id: 590223, title: "Love and Monsters", desc: undefined, banner: "/lA5fOBqTOQBQ1s9lEYYPmNXoYLi.jpg", type: "movie"},
    {id: 76479, title: "The Boys", desc: "A group of vigilantes known informally as “The Boys” set out to take down corrupt superheroes with no more than blue-collar grit and a willingness to fight dirty.", banner: "/mGVrXeIjyecj6TKmwPVpHlscEmw.jpg", type: "tv"}
])
...
```

Now we need a function that determines if a given movie/show is already contained in either the _Favorites_ or _History_ collection. If it is present, it removes it, if not present it adds it.

Lets talk about what is happening here. In short, we use a [jotai-optics](https://github.com/c0d3t3k/jotai-optic) wrapped [optics-ts](https://github.com/akheron/optics-ts) [isomorphism](https://degoes.net/articles/fp-glossary#:~:text=Isomorphism) to transform the internally passed atom collection passed by the outer `focus` call.

Because we need to track both the current and converted boolean value, we create a wrapper object within the optic that has two properties (`contained` and `value`). The `contained` property tracks the boolean output of the optic and the `value` property tracks the array that potentially contains the specified item.


```js
// optics.js
export const containsOptic = (item) => {

	return O.optic()
		.iso(
			// Lens that is isomorphically converting an array given an item 
			// to a boolean determining whether the array contains that item.
			(val) => ({ 
				contained: (item && item.id) ? (_.findIndex(val, (currentItem) => item.id == currentItem.id) > -1) : false,
				value: val
			}),
			(obj) => {
				if(!(item && item.id)) {
					return collection;
				}

				const collection = _.clone(obj.value);

				const index = _.findIndex(collection, (currentItem) => item.id == currentItem.id);

				if(obj.contained && index < 0) {
					collection.push(item);
				} else if(!obj.contained && index > -1) {
					collection.splice(index, 1);
				}

				return collection;
			}
		)
		.prop('contained');
```

To keep things relatively simple in the `BigTile.js`, `Tile.js` and `Hero.js` files we call our `containsOptic` factory function above to instantiate an optic that will provide not only History and Favorite state, but a way to easily set it. 

```js
// Tile.js excerpt

...
function Tile({data}) {

	// https://github.com/merisbahti/jotai-optics
	const [isInHistory, setIsInHistory] = 
		useAtom(focus(historyAtom, optic => optic.compose(containsOptic(data))))
	const [isFavorite, setIsFavorite] = 
        useAtom(focus(favoritesAtom, optic => optic.compose(containsOptic(data))))
        
```

Finally, we'll add some [icon](https://react-icons.github.io/react-icons/) buttons to call the respective setters created by the `jotai-optics` `focus` method above, to mutate the Favorites and History state.

```js
// Continued Tile.js excerpt

	const toggleFavorites = () => {
		setIsFavorite(!isFavorite);
	}
	const playMedia = () => {
		setIsInHistory(!isInHistory);
    }
    
    ...
    <button className="tile__play"  onClick={() => toggleFavorites()}>
        {isFavorite ? <AiFillHeart /> : <AiOutlineHeart />}
    </button>
    ...
    <button className="tile__play" onClick={playMedia}>
        <img className="tile__icon" src={require('../images/streamline-icon-controls-play@15x15.png')} alt=""/>
    </button>
...
```

And that about does it!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vvkx21eebv85fjxf1fz4.gif)

### Final Thoughts:
- Using an [optics](https://degoes.net/articles/fp-glossary#:~:text=Optics) based implementation ensures that state mutations can be modular and concise.
- With the [@akeron](https://github.com/akheron)'s [`optics-ts`](https://github.com/akheron/optics-ts) library, powerful optics can be constructed, leading to easily repeatable patterns and clean architecture
- [@merisbahti](https://github.com/merisbahti)'s [`jotai-optics`](https://github.com/merisbahti/jotai-optics) provides a straightforward integration between Jotai and `optics-ts`.
- Obviously, this was a very simple integration, but we feel it cracks open the door for some powerful functional programming integrations between [Jotai](https://github.com/pmndrs/jotai) and [jotai-optics](https://github.com/c0d3t3k/jotai-optics) especially in light of the impressive feature set of [optics-ts](https://github.com/akheron/optics-ts)


[Codesandbox example](https://codesandbox.io/s/jotaiflix-optics-9qch0) is included below. 

NOTE: This sample code includes [Jotai Dev Tools](https://github.com/c0d3t3k/jotai-devtools) so be sure to use a [Redux DevTools Browser Extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) to easily observe the relevant state changes. For more info, please see our [previous article](https://dev.to/c0d3t3k/jotai-not-exactly-redux-dev-tools-57i6). 


{% codesandbox jotaiflix-optics-9qch0 %}