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-recoil-adapter

v0.4.0

Published

Drop-in adapter for migrating to Jotai from Recoil

Downloads

374

Readme

jotai-recoil-adapter

jotai-recoil-adapter is a library designed to facilitate the transition from Recoil to Jotai in React applications. It provides a drop-in Recoil-compatible API built on top of Jotai's state management capabilities.

Quick Start

npm i jotai-recoil-adapter

The adapter supports the following Recoil features:

import {
  // standard Recoil APIs
  RecoilRoot,
  atom,
  atomFamily,
  selector,
  selectorFamily,
  useRecoilCallback,
  useRecoilState,
  useRecoilValue,
  useSetRecoilState,
  useResetRecoilState,
  useRecoilBridgeAcrossReactRoots_UNSTABLE,
  // special adapters for compatibility
  atomAsync,
  atomFamilyAsync,
  asyncSelector,
  asyncSelectorFamily,
  selectorDefault,
  // non-standard adaptations, see Readme
  waitForAll,
} from 'jotai-recoil-adapter';

Please read the Use Case and Warnings and Guidance section before use in business applications.

Usage

RecoilRoot

Convenience adapter for Recoil's <RecoilRoot /> component.

This component does nothing. It merely returns a <React.Fragment /> that wraps child components.

import { RecoilRoot } from 'jotai-recoil-adapter';

const App = () => <RecoilRoot> /* ... */ </RecoilRoot>

atom

Basic atom creation:

import { atom } from 'jotai-recoil-adapter';

const textState = atom({
  key: 'textState',
  default: 'Hello',
  effects: "UNSUPPORTED"
});

atomAsync

Adapter for Recoil atom that are initialized with an async selector. To be used with the selectorDefault adapter as a default value.

interface AtomAsyncAdapterParams<T, U> {
  ...,
  default: Promise<T>;
  effects?: "UNSUPPORTED";
  fallback?: U;
};
import { atom, atomAsync, selectorDefault } from 'jotai-recoil-adapter';

const userIdState = atom({
  key: 'userId',
  default: 1,
});

const userAtom = atomAsync({
  key: 'userAtom',
  default: selectorDefault({
    key: 'userAtomDefaultValueSelector',
    get: async ({ get }) => {
      const userId = get(userIdState);
      const response = await fetch(`/api/user/${userId}`);
      return response.json();
    },
  }),
  fallback: /* ... */
});

Why is the API different from Recoil? Jotai's API isn't perfectly compatible with Recoil's. Remember: this adapter exists to help ease the migration from Recoil to Jotai.

Important: This adapter is implemented using Jotai's getDefaultStore method, therefore this is incompatible with custom Jotai store providers and must only be used in Jotai providerless mode.

selector

Basic selector usage:

import { atom, selector } from 'jotai-recoil-adapter';

const countState = atom({
  key: 'count-state',
  default: Math.PI
});

const doubleCountSelector = selector({
  key: 'double-count',
  get: ({ get }) => get(countState) * 2,
});

Composing Jotai Atoms with jotai-recoil-adapter

jotai-recoil-adapter allows seamless composition of Jotai atom and atomFamily with its selector, selectorFamily, and useRecoilCallback. This enables complex state management patterns while maintaining a consistent API.

In this example, a Jotai PrimitiveAtom is used with a selector from jotai-recoil-adapter. This pattern is extendable to other combinations, allowing you to take full advantage of Jotai's capabilities within a Recoil-like API.

import { atom as jotaiAtom } from 'jotai';
import { selector, useRecoilValue } from 'jotai-recoil-adapter';

const countState = jotaiAtom(0);

const doubleCountSelector = selector({
  key: '...',
  get: ({ get }) => get(countState) * 2,
});

selectorDefault

Special adapter for both synchronous and asynchronous Recoil selector that are used to initialize recoil atom.

import {
  atom,
  atomFamily,
  selectorDefault
} from 'jotai-recoil-adapter';

const idState = atom({
  key: '...',
  default: 1,
});

const itemAtom = atom({
  key: '...',
  default: selectorDefault({
    key: '...',
    get: ({ get }) => {
      const id = get(idState);
      return createItem(id);
    },
  }),
});

const itemStateFamily = atomFamily({
  key: '...',
  default: (itemId: string) => `Item ${itemId}`,
});

const itemStateFamily = atomFamily({
  key: '...',
  default: selectorDefault({
    key: '...',
    get: ({ get }) => {
      ...
    }
  }),
});

Important: This adapter is implemented using Jotai's getDefaultStore method, therefore this is incompatible with custom Jotai store providers and must only be used in Jotai providerless mode.

selector (asynchronous) as asyncSelector

Asynchronous selector implemented using a special adapter:

type AsyncRecoilSelectorOptions<T, U> = {
  key: string;
  get: ({ get }) => Promise<T>;
  fallback: U;
};
import { asyncSelector } from 'jotai-recoil-adapter';

const randomNumberSelector = asyncSelector<number, "Crunching numbers...">({
  key: 'randomNumberSelector',
  get: async ({ get }) => {
    await new Promise(resolve => setTimeout(resolve, 1000));
    return Math.random();
  },
  fallback: "Crunching numbers..."
});

const userDataSelector = asyncSelector<User, null>({
  key: 'userData',
  get: async ({ get }) => {
    const response = await fetch('/api/user/data');
    return response.json();
  },
  fallback: null
});

selectorFamily

Atom family for creating a series of atoms based on parameters:

import { selectorFamily } from 'jotai-recoil-adapter';

const itemStateFamily = selectorFamily({
  key: '...',
  default: (itemId: string) => ({ get }) => {
    /* ... */
  },
});

selectorFamilyDefault

Special adapter for both synchronous and asynchronous Recoil selectorFamily that are used to initialize recoil atomFamily.


import {
  atomFamily,
  selectorDefaultFamily
} from 'jotai-recoil-adapter';

const itemStateFamily = atomFamily({
  key: '...',
  default: selectorDefaultFamily({
    key: '...',
    get: (itemId: string) => ({ get }) => {
      ...
    }
  }),
});

Important: This adapter is implemented using Jotai's getDefaultStore method, therefore this is incompatible with custom Jotai store providers and must only be used in Jotai providerless mode.

atomFamily

Atom family for creating a series of atoms based on parameters:

import {
  atomFamily,
  selectorDefault,
  selectorDefaultFamily
} from 'jotai-recoil-adapter';

const itemStateFamily = atomFamily({
  key: `item-state-family`,
  default: (itemId: string) => `Item ${itemId}`,
});

const itemStateFamily = atomFamily({
  key: '...',
  default: selectorDefault({
    key: '...',
    get: ({ get }) => {
      ...
    }
  }),
});

const itemStateFamily = atomFamily({
  key: '...',
  default: selectorDefaultFamily({
    key: '...',
    get: (itemId: string) => ({ get }) => {
      ...
    }
  }),
});

atomFamilyAsync

Adapter for Recoil atomFamily that are initialized with an async selector. To be used with the selectorDefaultFamily and selectorDefault adapters as a default values:

interface AtomFamilyAsyncAdapterParams<T, Param, U> {
  default: Promise<T>;
  effects?: "UNSUPPORTED";
  fallback?: U;
}
import { selectorDefault, selectorDefaultFamily, atomAsync } from 'jotai-recoil-adapter';
 
const fooStateFamily = atomFamilyAsync({
  key: 'foo-atom-family',
  default: selectorDefaultFamily({
     key: 'foo-atom-family-default-value-selector',
     get: (param) => async ({ get }) => {
      const data = await fetchData(`v1/api/data/${ param }`);
      const composedValue = get(someOtherAtom);
      return doStuffWith(composedValue, data);
     }
  }),
  fallback: /* ... */
});

const barStateFamily = atomFamilyAsync({
  key: 'bar-atom-family',
  default: selectorDefault({
     key: 'bar-atom-family-default-value-selector',
     get: async ({ get }) => {
      const data = await fetchData();
      const composedValue = get(someOtherAtom);
      return doStuffWith(composedValue, data);
     }
  }),
  fallback: /* ... */
});

Important: This adapter is implemented using Jotai's getDefaultStore method, therefore this is incompatible with custom Jotai store providers and must only be used in Jotai providerless mode.

useRecoilState

Hook to read and write atom state:

import { useRecoilState } from 'jotai-recoil-adapter';

const [text, setText] = useRecoilState(textState);

useRecoilValue

Hook to read atom or selector state:

import { useRecoilValue } from 'jotai-recoil-adapter';

const count = useRecoilValue(countSelector);

useSetRecoilState

Hook to update atom state:

import { useSetRecoilState } from 'jotai-recoil-adapter';

const setCount = useSetRecoilState(countState);

useRecoilCallback

Hook to interact with multiple atoms/selectors:

import { useRecoilCallback } from 'jotai-recoil-adapter';

const logCount = useRecoilCallback(
  ({ snapshot }) => async () => {
    const count = await snapshot.getPromise(countState);
    console.log(count);
  },
  [...]
);

useRecoilBridgeAcrossReactRoots_UNSTABLE (use with React portal)

Use Jotai's Provider component.

useRecoilBridgeAcrossReactRoots_UNSTABLE (use with react-three-fiber)

Recommendation is to use Jotai's "providerless mode".

see: https://github.com/pmndrs/jotai/issues/683#issuecomment-995764515

Use-Case

The primary use-case for jotai-recoil-adapter is to facilitate an incremental migration from Recoil to Jotai. This is particularly useful in scenarios where:

  • Large Codebases: Applications with a significant investment in Recoil can migrate to Jotai gradually, without needing a complete rewrite.
  • Testing and Stability: It allows for piece-by-piece migration and testing, ensuring that the application remains stable and reliable throughout the process.
  • Learning Curve: Teams can adapt to Jotai's concepts and APIs at a comfortable pace, reducing the learning curve.
  • Coexistence: Enables the coexistence of Recoil and Jotai during the transition period, allowing for a phased-out deprecation of Recoil.

This approach minimizes disruption in development workflows and provides a path to leverage Jotai's simplicity and performance benefits without the upfront cost of a full-scale migration.

Warnings and Guidance

Performance Concerns

While jotai-recoil-adapter aims to provide a seamless transition from Recoil to Jotai, there are potential performance implications to consider:

  • Overhead: The adapter introduces an additional abstraction layer, which could lead to slight performance overhead compared to using Jotai or Recoil directly.
  • Optimization Differences: Jotai and Recoil have different optimization strategies. Be aware that performance characteristics may change when migrating from Recoil to Jotai.

You should thoroughly test and measure your application before and after applying this adapter to insure against regressions.

Other Potential Concerns

  • Context Propagation: The context propagation mechanisms in Jotai and Recoil are different. This could lead to unexpected behavior, especially in complex component trees or when using context-dependent features.
  • Error Handling: The error handling paradigms in Jotai and Recoil might differ. Pay attention to how errors are handled and propagated in your application after the migration.

Incompleteness of Recoil API in jotai-recoil-adapter

jotai-recoil-adapter does not cover the entire API surface of Recoil. Some advanced features and utilities provided by Recoil might not have equivalents in this adapter.

It's important to review your current usage of Recoil and determine if any advanced features critical to your application are not supported by the adapter. Plan for alternative solutions or adjustments in such cases.

Contributing

Contributions to jotai-recoil-adapter are welcome. To contribute, fork the repository, create a feature branch, commit your changes, and open a Pull Request.

See CONTRIBUTING.md

Support and Issues

For issues or support questions, please file an issue on the GitHub repository.

License

jotai-recoil-adapter is released under the MIT License.