npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

jotai-wrapper

v1.0.3

Published

a wrapper for jotai library

Downloads

14

Readme

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}
    </>
  );
}