Recoil is an experimental state management framework for React applications that provides atoms and selectors for fine-grained reactivity.
—
Core functions for defining atoms and selectors that form the foundation of Recoil's state graph. Atoms represent units of state, while selectors represent derived state or computations.
Creates a unit of state that components can read from and write to.
/**
* Creates an atom, which represents a piece of writeable state
*/
function atom<T>(options: AtomOptions<T>): RecoilState<T>;
type AtomOptions<T> = {
/** Unique string identifying this atom */
key: string;
/** Default value for the atom */
default?: T | RecoilValue<T> | Promise<T> | Loadable<T> | WrappedValue<T>;
/** Array of effects to run when atom is first used */
effects?: ReadonlyArray<AtomEffect<T>>;
/** Allow direct mutation of atom values (use with caution) */
dangerouslyAllowMutability?: boolean;
};Usage Examples:
import { atom } from 'recoil';
// Simple atom with primitive default
const countState = atom({
key: 'countState',
default: 0,
});
// Atom with object default
const userState = atom({
key: 'userState',
default: {
id: null,
name: '',
email: '',
},
});
// Atom with async default
const userProfileState = atom({
key: 'userProfileState',
default: fetch('/api/user').then(res => res.json()),
});
// Atom with effects
const persistedState = atom({
key: 'persistedState',
default: '',
effects: [
({setSelf, onSet}) => {
// Initialize from localStorage
const saved = localStorage.getItem('persistedState');
if (saved != null) {
setSelf(JSON.parse(saved));
}
// Save to localStorage on changes
onSet((newValue) => {
localStorage.setItem('persistedState', JSON.stringify(newValue));
});
},
],
});Utility for preventing automatic unwrapping of values in atoms and selectors.
namespace atom {
/**
* Wraps a value to prevent unwrapping by Recoil
*/
function value<T>(value: T): WrappedValue<T>;
}
interface WrappedValue<T> {
readonly [WrappedValue_OPAQUE]: true;
}Usage Examples:
import { atom } from 'recoil';
// Prevent unwrapping of promises
const promiseState = atom({
key: 'promiseState',
default: atom.value(Promise.resolve('value')), // Promise won't be unwrapped
});
// Prevent unwrapping of Recoil values
const wrappedAtomState = atom({
key: 'wrappedAtomState',
default: atom.value(someOtherAtom), // Atom won't be read
});Creates derived state that can depend on atoms or other selectors.
/**
* Creates a selector which represents derived state
*/
function selector<T>(options: ReadWriteSelectorOptions<T>): RecoilState<T>;
function selector<T>(options: ReadOnlySelectorOptions<T>): RecoilValueReadOnly<T>;
interface ReadOnlySelectorOptions<T> {
/** Unique string identifying this selector */
key: string;
/** Function that computes the selector's value */
get: (opts: {
get: GetRecoilValue;
getCallback: GetCallback;
}) => T | RecoilValue<T> | Promise<T> | Loadable<T> | WrappedValue<T>;
/** Cache policy for this selector */
cachePolicy_UNSTABLE?: CachePolicyWithoutEquality;
/** Allow direct mutation of selector values (use with caution) */
dangerouslyAllowMutability?: boolean;
}
interface ReadWriteSelectorOptions<T> extends ReadOnlySelectorOptions<T> {
/** Function that handles setting the selector's value */
set: (opts: {
set: SetRecoilState;
get: GetRecoilValue;
reset: ResetRecoilState;
}, newValue: T | DefaultValue) => void;
}Usage Examples:
import { atom, selector, DefaultValue } from 'recoil';
const textState = atom({
key: 'textState',
default: '',
});
// Read-only selector
const charCountState = selector({
key: 'charCountState',
get: ({get}) => {
const text = get(textState);
return text.length;
},
});
// Async selector
const userNameState = selector({
key: 'userNameState',
get: async ({get}) => {
const userID = get(currentUserIDState);
const response = await fetch(`/api/users/${userID}`);
return response.json();
},
});
// Read-write selector
const tempFahrenheit = selector({
key: 'tempFahrenheit',
get: ({get}) => {
const tempCelsius = get(tempCelsiusState);
return (tempCelsius * 9) / 5 + 32;
},
set: ({set}, newValue) => {
const tempCelsius = ((newValue as number) - 32) * 5 / 9;
set(tempCelsiusState, tempCelsius);
},
});
// Selector with error handling
const safeDataState = selector({
key: 'safeDataState',
get: async ({get}) => {
try {
const data = await get(asyncDataState);
return data;
} catch (error) {
return { error: error.message };
}
},
});Utility for preventing automatic unwrapping of values in selectors.
namespace selector {
/**
* Wraps a value to prevent unwrapping by Recoil
*/
function value<T>(value: T): WrappedValue<T>;
}Pre-built selectors for common use cases.
/**
* Returns a selector that always has a constant value
*/
function constSelector<T extends SerializableParam>(constant: T): RecoilValueReadOnly<T>;
/**
* Returns a selector which is always in the provided error state
*/
function errorSelector(message: string): RecoilValueReadOnly<never>;
/**
* Casts a selector to be a read-only selector
*/
function readOnlySelector<T>(atom: RecoilValue<T>): RecoilValueReadOnly<T>;Usage Examples:
import { constSelector, errorSelector, readOnlySelector } from 'recoil';
// Constant selector
const appVersionState = constSelector('1.0.0');
// Error selector for testing
const errorState = errorSelector('This always errors');
// Read-only wrapper
const readOnlyUserState = readOnlySelector(userState);System for adding side effects to atoms when they are first used.
type AtomEffect<T> = (param: {
node: RecoilState<T>;
storeID: StoreID;
trigger: 'set' | 'get';
setSelf: (param: T | DefaultValue | Promise<T | DefaultValue> | WrappedValue<T> |
((param: T | DefaultValue) => T | DefaultValue | WrappedValue<T>)) => void;
resetSelf: () => void;
onSet: (param: (newValue: T, oldValue: T | DefaultValue, isReset: boolean) => void) => void;
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>;
getLoadable: <S>(recoilValue: RecoilValue<S>) => Loadable<S>;
getInfo_UNSTABLE: <S>(recoilValue: RecoilValue<S>) => RecoilStateInfo<S>;
}) => void | (() => void);Usage Examples:
import { atom } from 'recoil';
// Persistence effect
const localStorageEffect = (key: string) => ({setSelf, onSet}) => {
const savedValue = localStorage.getItem(key);
if (savedValue != null) {
setSelf(JSON.parse(savedValue));
}
onSet((newValue, _, isReset) => {
isReset
? localStorage.removeItem(key)
: localStorage.setItem(key, JSON.stringify(newValue));
});
};
// Logging effect
const loggingEffect = ({onSet, node}) => {
onSet((newValue, oldValue) => {
console.log(`${node.key} changed from`, oldValue, 'to', newValue);
});
};
// Atom with effects
const trackedState = atom({
key: 'trackedState',
default: '',
effects: [
localStorageEffect('tracked-state'),
loggingEffect,
],
});Configuration for selector result caching and eviction.
type EvictionPolicy = 'lru' | 'keep-all' | 'most-recent';
type CachePolicyWithoutEquality =
| {eviction: 'lru', maxSize: number}
| {eviction: 'keep-all'}
| {eviction: 'most-recent'};Install with Tessl CLI
npx tessl i tessl/npm-recoil