Skip to main content

Command Palette

Search for a command to run...

Understanding Selectors in Recoil

Updated
5 min read

What Are Selectors?

A selector is a pure function in Recoil that derives or computes state based on atoms or other selectors. It’s similar to the concept of "computed properties" in other frameworks (like Vue.js), where the value of the selector changes when its dependencies change.

  • Selectors can either get or set state.

  • They can be thought of as a "view" on top of state: a transformation of atoms and/or other selectors.

Basic Selector Example

Let’s break down how a selector works. Imagine you have an atom that holds a piece of state (like a string of text), and you want to compute the length of that string. The selector derives this length from the atom.

1. Define an Atom:

An atom is a piece of state in Recoil that components can subscribe to.

import { atom } from 'recoil';

export const textState = atom({
  key: 'textState',  // Unique ID for this atom
  default: '',       // Default value (initial state)
});

2. Define a Selector:

A selector derives or computes new state based on the atom.

import { selector } from 'recoil';
import { textState } from './atoms';

export const charCountState = selector({
  key: 'charCountState',  // Unique ID for this selector
  get: ({ get }) => {      // `get` allows access to atoms/selectors
    const text = get(textState);  // Get the value of `textState`
    return text.length;           // Return the length of the text
  },
});
  • Key: A unique identifier for the selector.

  • Get: A function that calculates the derived value. The get function has access to the state using get().

3. Using a Selector in a Component:

You can use the useRecoilValue hook to retrieve the computed value from the selector.

import { useRecoilValue } from 'recoil';
import { charCountState } from './selectors';

function CharacterCount() {
  const count = useRecoilValue(charCountState);  // Get the derived value
  return <div>Character Count: {count}</div>;
}
  • When the textState atom changes, the charCountState selector will recompute, and any component subscribed to the selector will re-render.

Selector Caching

Recoil automatically caches the value of selectors. When a selector is re-evaluated, Recoil will use the cached value if the underlying state (atoms/selectors) has not changed. This caching mechanism ensures efficient re-renders in large applications.

Asynchronous Selectors

Selectors in Recoil can also be asynchronous. They can fetch data from APIs, perform async operations, or return promises that resolve later.

Example: Fetching Data with an Async Selector

Here’s how you can define a selector that fetches data from an API:

import { selector } from 'recoil';

export const userDataState = selector({
  key: 'userDataState',
  get: async () => {
    const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
    const data = await response.json();
    return data;  // Return the fetched data
  },
});
  • Async Selector: The get function can return a Promise. Recoil will automatically handle the state while the promise is resolving (such as showing loading states in components).

Using the Async Selector in a Component:

import { useRecoilValue } from 'recoil';
import { userDataState } from './selectors';

function UserInfo() {
  const user = useRecoilValue(userDataState);  // Read the fetched data

  return (
    <div>
      <h1>{user.name}</h1>
      <p>Email: {user.email}</p>
    </div>
  );
}

Recoil automatically handles loading and errors when dealing with async selectors. If the async operation is ongoing, the component will suspend rendering, which is especially useful if you are using React Suspense.

Writing to Selectors

In addition to reading (getting) derived state, Recoil also supports writing to selectors by providing a set function. This allows selectors to act as both readers and writers of state.

Example: A Read/Write Selector

Suppose you want to create a selector that reads from an atom but also allows you to set a derived value. Here’s an example that manages a text state in lowercase:

import { selector } from 'recoil';
import { textState } from './atoms';

export const lowercaseTextState = selector({
  key: 'lowercaseTextState',
  get: ({ get }) => {
    const text = get(textState);    // Get the current value of `textState`
    return text.toLowerCase();      // Return it in lowercase
  },
  set: ({ set }, newValue) => {
    set(textState, newValue.toUpperCase());  // Set the original state in uppercase
  },
});
  • Get Function: Returns the lowercase version of textState.

  • Set Function: When you try to update lowercaseTextState, it updates textState with the uppercase value.

Using the Read/Write Selector:

import { useRecoilState } from 'recoil';
import { lowercaseTextState } from './selectors';

function TextTransformer() {
  const [text, setText] = useRecoilState(lowercaseTextState);  // Access and modify the derived state

  const onChange = (event) => {
    setText(event.target.value);  // Updates the state with uppercase
  };

  return (
    <div>
      <input type="text" value={text} onChange={onChange} />
      <p>Lowercase: {text}</p>
    </div>
  );
}

When the user types, the input value is stored in uppercase, but what the user sees is the lowercase transformation.

Dependent Selectors

Selectors can depend on other selectors. This allows you to build complex state dependencies with ease. For example, you could have one selector that processes raw data, and another selector that refines or filters that processed data.

export const filteredListState = selector({
  key: 'filteredListState',
  get: ({ get }) => {
    const list = get(someListState);
    const filter = get(filterState);
    return list.filter(item => item.includes(filter));
  },
});
  • In this case, filteredListState depends on both someListState and filterState. It will automatically update if either of those atoms change.

When to Use Selectors?

  • Derived State: If you need to compute or derive values from the state (e.g., a transformation like filtering or aggregating data).

  • Shared Logic: When multiple components need access to the same computed state.

  • Asynchronous Data: For fetching data from APIs or performing async computations.

  • Composing State: When your state depends on multiple atoms or selectors, making selectors ideal for managing more complex or interdependent state.

Selector Best Practices

  • Keep Selectors Pure: Always ensure the get function is pure—no side effects, only transformations of atoms/selectors.

  • Efficiency: Since selectors are cached, try to avoid complex, unnecessary computations inside them.

  • Composability: Use selectors to compose state, so individual components don’t have to handle the complexity of computing derived state themselves.


Selectors are a powerful tool in Recoil for managing derived, computed, and async state, and they provide an efficient, declarative way to manage complex state dependencies in your React applications.