Skip to main content

· One min read
  • Fix potential unhandled promise rejection in useRecoilCallback() (#2075)
  • Add OSS support for GateKeeper feature toggling via RecoilEnv.RECOIL_GKS_ENABLED (#2078)
  • Fix resolving suspense of async selectors used with SSR (#2073, #1960)
  • Fix SSR with some versions of React DOM used with Next.JS 13 (#2082, #2086)

· One min read
  • Expose flag to disable "duplicate atom key" checking / logging, as it was too noisy in environments such as NextJS or some dev environments using Fast Refresh. (#733, #2020, #2046)

    • Import RecoilEnv from the recoil package, and set RecoilEnv.RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED = false in code to disable the checking and logging.
    • We also support process.env.RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false in NodeJS environments such as NextJs
    • Caution: This disables all checks for duplicate atom keys including legitimate errors, so use with caution!
  • Workaround for React 18 environments with nested renderers that don't support useSyncExternalStore(). (#2001, #2010)

· One min read
  • Export updateItems() for the listen prop callback in <RecoilSync> in addition to updateItem() and updateAllKnownItems(). (#2017, #2035)
  • Removing a parameter from the URL will reset atoms when using location queryParams with a param. This is a slight breaking change when an atom might sync with multiple URL params. (#1900, #1976)
  • Add a dev warning if an unstable handlers prop is detected for <RecoilURLSyncTransit>. (#2044)

· One min read
  • Rename boolean() export to bool() since boolean is a reserved word (#1922, #1962, #1971)
  • Remove reference to native directory in package.json to cleanup errors for react-native. (#1931)
  • Export Path class for custom checkers. (#1950, #1956)
  • Extend the failure message of union() and or() with each type. (#1961)

· One min read
  • Fix useRecoilSnapshot() with React's Fast Refresh during development (#1891)
  • Fix useRecoilSnapshot() and recoil-sync with changed browser behavior starting with Chrome v104 (#1943, #1936)

· 3 min read

Initial open source release for the recoil-sync NPM package! Recoil Sync provides an add-on library to help synchronize Recoil state with external systems. Simple asynchronous data queries can be implemented via selectors or useEffect(), or atom effects can be used for bi-directional syncing of individual atoms. The recoil-sync add-on package provides some additional functionality:

  • Batching Atomic Transactions - Updates for multiple atoms can be batched together as a single transaction with the external system. This can be important if an atomic transaction is required for consistent state of related atoms.
  • Abstract and Flexible - This API allows users to specify what atoms to sync separately from describing the mechanism of how to sync. This allows components to use atoms and sync with different systems in different environments without changing their implementation. For example, a component may use atoms that persist to the URL when used in a stand-alone tool while persisting to a custom user database when embedded in another tool.
  • Validation and Backward Compatibility - When dealing with state from external sources it is important to validate the input. When state is persisted beyond the lifetime of an app it can also be important to consider backward compatibility of previous versions of state. recoil-sync and refine help provide this functionality.
  • Complex Mapping of Atoms to External Storage - There may not be a one-to-one mapping between atoms and external storage items. Atoms may migrate to use newer versions of items, may pull props from multiple items, just a piece of some compound state, or other complex mappings.
  • Sync with React Hooks or Props - This library enables syncing atoms with React hooks or props that are not accessible from atom effects.

The recoil-sync library also provides built-in implementations for external stores, such as syncing with the browser URL.

The basic idea is that a syncEffect() can be added to each atom that you wish to sync, and then a <RecoilSync> is added inside your <RecoilRoot> to specify how to sync those atoms. You can use built-in stores such as <RecoilURLSyncJSON>, make your own, or even sync different groups of atoms with different stores.


URL Persistence

Here is a simple example syncing an atom with the browser URL:

const currentUserState = atom<number>({
key: 'CurrentUser',
default: 0,
effects: [
syncEffect({ refine: number() }),

Then, at the root of your application, simply include <RecoilURLSyncJSON> to sync all of those tagged atoms with the URL

function MyApp() {
return (
<RecoilURLSyncJSON location={{part: 'queryParams'}}>

That's it! Now this atom will initialize its state based on the URL during initial load, any state mutations will update the URL, and changes in the URL (such as the back button) will update the atom. See more examples in the Sync Effect, Store Implementation, and URL Persistence guides.

· 2 min read

Initial open source release for the @recoiljs/refine library for type refinement and input validation for Flow and TypeScript! To get started learning about Refine, check out the documentation on the core concepts of Utilities and Checkers.

The Recoil Sync library leverages Refine for type refinement, input validation, and upgrading types for backward compatibility. See the recoil-sync docs for more details.

Why would I want to use Refine?

Type Refinement Example

Coerce unknown types to a strongly typed variable. assertion() will throw if the input doesn't match the expected type while coercion() will return null.

const myObjectChecker = object({
numberProperty: number(),
stringProperty: optional(string()),
arrayProperty: array(number()),

const myObjectAssertion = assertion(myObjectChecker);
const myObject: CheckerReturnType<myObjectChecker> = myObjectAssertion({
numberProperty: 123,
stringProperty: 'hello',
arrayProperty: [1, 2, 3],

Backward Compatible Example

Using match() and asType() you can upgrade from previous types to the latest version.

const myChecker: Checker<{str: string}> = match(
object({str: string()}),
asType(string(), str => ({str: str})),
asType(number(), num => ({str: String(num)})),

const obj1: {str: string} = coercion(myChecker({str: 'hello'}));
const obj2: {str: string} = coercion(myChecker('hello'));
const obj3: {str: string} = coercion(myChecker(123));

JSON Parser Example

Refine wraps JSON to provide a built-in strongly typed parser.

const myParser = jsonParser(
array(object({num: number()}))

const result = myParser('[{"num": 1}, {"num": 2}]');

if (result != null) {
// we can now access values in num typesafe way
assert(result[0].num === 1);
} else {
// value failed to match parser spec

· One min read

Initial open source release for the recoil-relay library for using GraphQL!

This library helps Recoil perform type safe and efficient queries using GraphQL with the Relay library. It provides selectors which can easily query with GraphQL. The queries are synced with the Recoil data-flow graph so downstream selectors can derive state from them, they can depend on upstream Recoil state, and they are automatically subscribed to any changes in the graph from Relay. Everything stays in sync automatically.


A GraphQL query is as simple as defining a GraphQL selector:

const userNameQuery = graphQLSelector({
key: 'UserName',
environment: myEnvironment,
query: graphql`
query UserQuery($id: ID!) {
user(id: $id) {
variables: ({get}) => ({id: get(currentIDAtom)}),
mapResponse: data => data.user?.name,

Then use it like any other Recoil selector:

function MyComponent() {
const userName = useRecoilValue(userNameQuery);
return <span>{userName}</span>;