DEV Community

Cover image for Recoil - Facebook's own State Management Library
Shubham Kamath
Shubham Kamath

Posted on • Edited on

Recoil - Facebook's own State Management Library

I have used Redux as my state management library extensively in projects. It takes time to set it up but once everything is up, there is no looking back.

Since it was sufficient for me, I never tried any options till yesterday when Recoil an experimental state management library by Facebook was launched.

Going through examples, I realised it's advantages over Redux, like :

  • Easy to set up and use
  • Supports asynchronous State Management
  • State persistence ( I'm still not sure how to implement, but I read regarding this in Source Code )

This got me like :

Shut up and take my money meme image

So how Recoil works?

It stores data in Atoms. React Components can subscribe to these atoms. The subscription can be used to get and set data from Atoms.

To get started, we need to understand few Recoil APIs

1. RecoilRoot

  • <RecoilRoot /> is used to wrap component, which needs access to Atoms.
  • Children of such components can also access Atoms.
  • Preferably, we wrap it around the root of application.
  • But, multiple roots can be present with each having different state of the same Atom.

2. Atom

  • Atom is where you can store state, accessible around the application.
  • It takes mainly two arguments, Unique key to identify the Atom and a default value to start with.

3. Selectors

  • Selector returns a modified state of an Atom.
  • It takes two argument, Unique Key and a get function that returns a modified state of the selected Atom.

Let's create a simple ToDo list app to implement Recoil

Create a simple create-react-app and clean it for a new project.

1. Let's wrap our Root Component i.e App Component in index.js with <RecoilRoot/> , this will enable Recoil State in app.

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {RecoilRoot} from 'recoil';

ReactDOM.render(
  <React.StrictMode>
    <RecoilRoot>
      <App />
    </RecoilRoot>
  </React.StrictMode>,
  document.getElementById("root")
);
Enter fullscreen mode Exit fullscreen mode

2. Let's create Atom and subscribe it to enable ToDo state in App.js

import React from 'react';
import { atom, useRecoilValue } from 'recoil';

const list = atom({
    key: "list",
    default: []
});

function App() {
  const listState = useRecoilValue(list);

  return (
    <div> 
     {
       listState.map(listItem => 
         <p key={listItem.id}>{listItem.value}</p>
       )
     }
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode
  • Using atom() we create list Atom and initialise it with a unique key and a default value.

  • Using useRecoilValue(list) we subscribe to any changes in list Atom while it returns current value of list.

3. Now to modify state of an Atom, there are two ways!

  1. Using useRecoilState(list) which returns an array just like useState() React hook. This array consists of list Atom value and a function which can modify list Atom state.

  2. Using useSetRecoilState(list) which returns a function which can modify list Atom state.

We will go with the useSetRecoilState(list) for this one.

import React, { useState } from 'react';
import { atom, useRecoilValue, useSetRecoilState } from 'recoil';
import { v4 as uuid4 } from 'uuid';

const list = atom({
    key: "list",
    default: []
});

function App() {
  const [inputValue, updateInputValue] = useState("");
  const listState = useRecoilValue(list);
  const updateList = useSetRecoilState(list);

  const changeValue = event => {
      const { value } = event.target;
      updateInputValue(value);
  }

  const addValue = () => {
     setInput("");
     updateList((oldList) => [
         ...oldList,
         {
           id: uuid4(),
           value: inputValue,
         },
     ]);
  }

  return (
    <div> 
        <div>
            <p>Enter item :</p>
            <input type="text" value={inputValue} onChange={e => changeValue(e)}/>
            <button className="addInputButton" onClick={() => addValue()}>Add</button>
         </div>

         {
             listState.map(listItem => 
                 <p key={listItem.id}>{listItem.value}</p>
              )
         }
    </div>
  );
}

export default App;


Enter fullscreen mode Exit fullscreen mode
  • The function returned by useSetRecoilState(list) takes a callback function as an argument.

  • The callback function returns a value which is set to the list Atom.

  • The first argument in callback function also holds the current state of list Atom, here, we can use it to append latest item in existing ToDo list.

4. Let's add Selector for our ToDo list!

import React, { useState } from 'react';
import { atom, useRecoilValue, useSetRecoilState, selector } from 'recoil';
import { v4 as uuid4 } from 'uuid';

const list = atom({
    key: "list",
    default: []
});

const filterListValue = atom({
    key: "filterListValue",
    default: "" 
});

const filterList = selector({
    key: "filterList",
    get: ({get}) => {
        const listState = get(list);
        const filterListValueState = get(filterListValue);

        if (filterListValueState.length) {
          return listState.filter((item) =>
            item.value.includes(filterListValueState) && item
          );
        }
        return list;
    }
})

function App() {
  const [inputValue, updateInputValue] = useState("");
  const listState = useRecoilValue(list);
  const updateList = useSetRecoilState(list);

  const changeValue = event => {
      const { value } = event.target;
      updateInputValue(value);
  }

  const addValue = () => {
     setInput("");
     updateList((oldList) => [
         ...oldList,
         {
           id: uuid4(),
           value: inputValue,
         },
     ]);
  }

  return (
    <div> 
        <div>
            <p>Enter item :</p>
            <input type="text" value={inputValue} onChange={e => changeValue(e)}/>
            <button className="addInputButton" onClick={() => addValue()}>Add</button>
         </div>

         {
             listState.map(listItem => 
                 <p key={listItem.id}>{listItem.value}</p>
              )
         }
    </div>
  );
}

export default App;


Enter fullscreen mode Exit fullscreen mode
  • Here we add one more Atom named filterListValue Atom which holds the filter query used by filterList Selector to filter list Atom.

  • Selector here filters list which contains query from filterListValue Atom.

  • When filterListValue Atom value is empty, filterList Selector returns whole list Atom.

  • Function that is assigned to Selector's get parameter is passed with Object as an argument. The get property of object is used to retrieve value from list Atom and filterListValue Atom.

5. Once Selector is added let's add functionality for filter

import React, { useState } from 'react';
import { atom, useRecoilValue, useSetRecoilState, selector } from 'recoil';
import { v4 as uuid4 } from 'uuid';

const list = atom({
    key: "list",
    default: []
});

const filterListValue = atom({
    key: "filterListValue",
    default: "" 
});

const filterList = selector({
    key: "filterList",
    get: ({get}) => {
        const listState = get(list);
        const filterListValueState = get(filterListValue);

        if (filterListValueState.length) {
          return listState.filter((item) =>
            item.value.includes(filterListValueState) && item
          );
        }
        return list;
    }
})

function App() {
  const [inputValue, updateInputValue] = useState("");

  const listState = useRecoilValue(list);
  const updateList = useSetRecoilState(list);

  const [filterListState,filterList] = useRecoilState(filterListValue);

  const changeValue = event => {
      const { value } = event.target;
      updateInputValue(value);
  }

  const addValue = () => {
     setInput("");
     updateList((oldList) => [
         ...oldList,
         {
           id: uuid4(),
           value: inputValue,
         },
     ]);
  }

  const filter = event => {
      const { value } = event.target;
      filterList(value);
  }

  const clearFilter = () => filterList("");


  return (
    <div> 
        <div>
            <p>Enter item :</p>
            <input type="text" value={inputValue} onChange={e => changeValue(e)}/>
            <button className="addInputButton" onClick={() => addValue()}>Add</button>
         </div>

         <div>
             <p>Filter : </p>
             <input
                type="text"
                value={filterListState}
                onChange={(e) => filter(e)}
             />

             <button onClick={() => clearFilter()}>
                Clear
             </button>
         </div>

         {
             listState.map(listItem => 
                 <p key={listItem.id}>{listItem.value}</p>
              )
         }
    </div>
  );
}

export default App;


Enter fullscreen mode Exit fullscreen mode
  • Would this work? No. Why? Because we have not subscribed to Selector yet, so it might filter ToDo list but won't reflect over the component.

  • So we make a small change in our code shown below

- const listState = useRecoilValue(list);
+ const listState = useRecoilValue(filterList); 
Enter fullscreen mode Exit fullscreen mode

This would complete the little ToDo Application with Add and Filter functionality. If you want to see a more structured approach, you can checkout GitHub repository below.

GitHub logo shubhaemk / recoil-example

Recoil state management library implementation

Let me know your opinion on Recoil. In next post I have explained asynchronous side of Recoil. Cheers!

Top comments (20)

Collapse
 
danwood profile image
Dan Wood

Can you elaborate on "Uses React Hooks unlike Redux" please?

react-redux.js.org/api/hooks

Collapse
 
barzi92367868 profile image
Barzi

Yes, that was my same reaction. I find that react-redux has great hooks, and I find them incredibly easy and fun to use...
Besides that, I liked the article. I didn't grasp 100% of the concepts, but it made me interested in recoil.

Collapse
 
shubhamk profile image
Shubham Kamath

First of all thanks a lot. Hope you make something amazing out of it.

Secondly, I apologise for "react-redux" . I should have researched if don't know about it. I'll make sure to correct it. 😌

Collapse
 
shubhamk profile image
Shubham Kamath

Thanks for pointing it out. I'm still new to hooks as I have be using class since ages. I'll correct it.

Collapse
 
stevetaylor profile image
Steve Taylor

It uses promises, which makes it a non-starter for me. Promises can’t be cancelled, so there’s no way to abort pending API requests when they’re no longer needed.

Collapse
 
shubhamk profile image
Shubham Kamath

Yes. Totally!

But if the application is tiny, this can be handy!

Collapse
 
stevetaylor profile image
Steve Taylor

Recoil looks like it’s made for non-trivial apps. It’s very promising (pun not intended) in many ways. It just needs to be a better fit with respect to async.

It’s a pity Observable isn’t yet standardized.

Thread Thread
 
shubhamk profile image
Shubham Kamath

It's recently launched, so there is plenty of room for improvement. What library you suggest based on Observables?

Thread Thread
 
stevetaylor profile image
Steve Taylor

There’s Redux Observable by Netflix.

I prefer Bacon.js over RxJS because it’s more expressive, less verbose, and uses hot observables instead of cold. I created react-baconjs to use it with React.

You could also use Bacon.js with Redux Observable as there’s some interop with standard(ish) observables.

I’d be interested to see how observables could be used with Recoil.

Thread Thread
 
shubhamk profile image
Shubham Kamath

I will surely try this. Thanks a lot sir!

Collapse
 
zarabotaet profile image
Dima • Edited

I am using effector, who solved these problems and many others more than a year ago.

Side effects in selectors its 🤯

Collapse
 
shubhamk profile image
Shubham Kamath

I am unaware of effector, I'll try it too.

JS is huge huge huge. 👀

Collapse
 
zarabotaet profile image
Dima

Effector - fast and powerful state manager

• statically typed
• multi-store
• less boilerplate by design
• computed values
• no need for memoization
• side-effect management
• framework agnostic
• observable interoperability
• relatively small size

codeburst.io/effector-state-manage...

Thread Thread
 
shubhamk profile image
Shubham Kamath

Thanks 😌

Collapse
 
gypsydave5 profile image
David Wickes

Reminds me of Clojure and ClojureScript libraries from about three years ago.

Collapse
 
shubhamk profile image
Shubham Kamath

Wow. Thanks for letting me know something that I would have never known otherwise in this vast pool of JS. 😌

Collapse
 
nickytonline profile image
Nick Taylor

Congrats on your first post!

1st place in Mariokart

Collapse
 
shubhamk profile image
Shubham Kamath

Thanks a lot Sir! 😬

Collapse
 
alexandrzavalii profile image
Alex Zavalii

thats how you could implement persistence:
github.com/facebookexperimental/Re...

Collapse
 
shubhamk profile image
Shubham Kamath

Thanks for letting me know. Recoil docs are changing rapidly though! 😅