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 :
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")
);
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;
Using
atom()
we createlist Atom
and initialise it with a unique key and a default value.Using
useRecoilValue(list)
we subscribe to any changes inlist Atom
while it returns current value oflist
.
3. Now to modify state of an Atom
, there are two ways!
Using
useRecoilState(list)
which returns an array just likeuseState()
React hook. This array consists oflist Atom
value and a function which can modifylist Atom
state.Using
useSetRecoilState(list)
which returns a function which can modifylist 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;
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;
Here we add one more
Atom
namedfilterListValue Atom
which holds the filter query used byfilterList Selector
to filterlist Atom
.Selector
here filters list which contains query fromfilterListValue Atom
.When
filterListValue Atom
value is empty,filterList Selector
returns wholelist Atom
.Function that is assigned to
Selector
's get parameter is passed with Object as an argument. Theget
property of object is used to retrieve value fromlist Atom
andfilterListValue 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;
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);
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.
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)
Can you elaborate on "Uses React Hooks unlike Redux" please?
react-redux.js.org/api/hooks
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.
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. 😌
Thanks for pointing it out. I'm still new to hooks as I have be using class since ages. I'll correct it.
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.
Yes. Totally!
But if the application is tiny, this can be handy!
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.It's recently launched, so there is plenty of room for improvement. What library you suggest based on Observables?
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.
I will surely try this. Thanks a lot sir!
I am using effector, who solved these problems and many others more than a year ago.
Side effects in selectors its 🤯
I am unaware of effector, I'll try it too.
JS is huge huge huge. 👀
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...
Thanks 😌
Reminds me of Clojure and ClojureScript libraries from about three years ago.
Wow. Thanks for letting me know something that I would have never known otherwise in this vast pool of JS. 😌
Congrats on your first post!
Thanks a lot Sir! 😬
thats how you could implement persistence:
github.com/facebookexperimental/Re...
Thanks for letting me know. Recoil docs are changing rapidly though! 😅