jotai-wrapper

1.0.3 • Public • Published

jotai-wrapper

Motivation

This library originates from the necessity to migrate a project with react-context-slices to jotai. react-context-slices and jotai share a similar API. In react-context-slices you use the useSlice hook, while in jotai you use the useAtom, useSetAtom, and useAtomValue hooks. In react-context-slices you define React Context or Redux slices, while in jotai you define atoms. So the migration from the first to the second is fairly simple. react-context-slices serves its purpose, but has a great amount of memory usage specially when using React Context slices.

The API

jotai-wrapper it's really a very tiny and simple library. This is its source code:

// index.js
import {
  atom,
  useAtom as jotaiUseAtom,
  useAtomValue as jotaiUseAtomValue,
  useSetAtom as jotaiUseSetAtom,
} from "jotai";

const fallbackAtom = atom();

export default function getAPIFromAtoms(atoms) {
  const atomsEntries = Object.entries(atoms);

  function useJotai(atomInput, jotaiHook) {
    if (typeof atomInput === "string") {
      const atomEntry = atomsEntries.find(([key]) => key === atomInput);
      if (atomEntry) {
        return jotaiHook(atomEntry[1]);
      }
      return jotaiHook(fallbackAtom);
    }
    return jotaiHook(atomInput ?? fallbackAtom);
  }

  function useAtom(atom) {
    return useJotai(atom, jotaiUseAtom);
  }

  function useAtomValue(atom) {
    return useJotai(atom, jotaiUseAtomValue);
  }

  function useSetAtom(atom) {
    return useJotai(atom, jotaiUseSetAtom);
  }

  function getAtom(atomName) {
    const atomEntry = atomsEntries.find(([key]) => key === atomName);
    if (atomEntry) {
      return atomEntry[1];
    }
    return fallbackAtom;
  }

  const selectAtom = (atomName, selector) => {
    return atom((get) => selector(get(getAtom(atomName))));
  };

  return { useAtom, useAtomValue, useSetAtom, getAtom, selectAtom };
}

As you can see it default exports a function, getAPIFromAtoms, which returns an object with three hooks (useAtom, useAtomValue, and useSetAtom) and two functions (getAtom, and selectAtom).

So the way to use it is also similar to the way you use react-context-slices. In react-context-slices you define a slices file and in jotai-wrapper you define an atoms file:

// atoms.js
import { atom } from "jotai";
import getAPIFromAtoms from "jotai-wrapper";

export const { useAtom, useSetAtom, useAtomValue, getAtom, selectAtom } =
  getAPIFromAtoms({
    counter: atom(0),
    todos: atom([]),
    messagesLastFoundId: atom(-1),
    invitationsLastFoundId: atom(-1),
    // rest of atoms
  });

Then, in your react components, you use it like this:

// counter.js
import {useAtom} from "./atoms";

export default function Counter(){
    const [counter,setCounter]=useAtom("counter");

    return <>
    <button onClick={()=>setCounter(c=>c+1)}>+</button>{counter}
    <>;
}

As in react-context-slices, where you use strings to refer to the slices, in jotai-wrapper you also use strings to refer to the atoms. This allows for dynamic referencing:

// use-last-found-id.js
import { useSetAtom } from "./atoms";

export function useLastFountId({ prefix }) {
  const setLastFoundId = useSetAtom(`${prefix}LastFoundId`);
  // ...
}

The hook useAtomValue is the same as in jotai, but using a string to refer to the atom (you can also pass an atom if you wish): const counter = useAtomValue("counter");

The getAtom function returns the atom which the string refers to: const counterAtom = getAtom("counter");

Finally, the selectAtom function you use it like this:

// todos.js
import { selectAtom, useAtomValue } from "./atoms";
import { useMemo } from "react";

export default function Todos({ index, id }) {
  const todoAtIndex = useAtomValue(
    useMemo(() => selectAtom("todos", (todos) => todos[index]), [index])
  );
  const todoWithId = useAtomValue(
    useMemo(
      () =>
        selectAtom("todos", (todos) => todos.find((todo) => todo.id === id)),
      [id]
    )
  );
  //...
}

As you can see from the source code shown, this function (selectAtom) from jotai-wrapper it's different than the selectAtom from jotai. So if you want to use the selectAtom from jotai, you must do import {selectAtom} from "jotai/utils".

Edge cases

As you can see from the source code, when calling any of the hooks with a key (string) that doesn't exist or with null or undefined, it uses a fallback atom defined in the library. The same for the getAtom and selectAtom functions (the second one depends on the first).

So the next component will render 11 when called:

import { useAtomValue, getAtom } from "./atoms";

export default function Edge() {
  const value1 = useAtomValue();
  const value2 = useAtomValue(null);
  const value3 = useAtomValue("");
  const atom1 = getAtom();
  const atom2 = getAtom(null);
  const atom3 = getAtom("");

  return (
    <>
      {value1 === value2 && value2 === value3 && 1}
      {atom1 === atom2 && atom2 === atom3 && 1}
    </>
  );
}

Readme

Keywords

Package Sidebar

Install

npm i jotai-wrapper

Weekly Downloads

4

Version

1.0.3

License

ISC

Unpacked Size

7.83 kB

Total Files

4

Last publish

Collaborators

  • roggc