4 min read
·
By Emil Mork
·
December 10, 2020
Recoil.js is a new state management library for React — offering a React-ish API and a simple and powerful way of dealing with global, asynchronous and derived state 🔥
Recoil aims to solve some specific challenges when working with modern React apps like flexible shared state, derived state and global observation. And like React, the original Recoil team works at Facebook.
If you have worked with React it should seem familiar. Just take a look at this example. First we use React with useState()
to increment a number:
// React and setState
const Counter = () => {
const [count, setCount] = useState(0);
return (
<>
<span>Count: {count}</span>
<button onClick={() => setCount(count + 1)}>Increment</button>
</>
);
};
Now, making our state global is pretty straightforward using Recoil. All we need to do (almost 😇) is to replace useState
with useRecoilState
.
//React and Recoil
const Counter = () => {
const [count, setCount] = useRecoilState(myGlobalCounterState); ⬅
return (
<>
<span>Count: {count}</span>
<button onClick={() => setCount(count + 1)}>Increment</button>
</>
);
};
That's pretty cool! Not convinced? Keep on reading... 😄
The examples below are taken from a simple demo application called FoodTalk. The app lets you sort, filter and search for recipes. Take a look at the demo or the source code on github.
Its a simple app, but it uses Recoil to solve some known challenges pretty well.
An atom is simply a piece of state. It's like using react's useState()
, except that any component can subscribe to it. By updating the atom value, all subscribed components will be re-rendered. In our recipe application we use atoms to store different types of state, like a search input:
// store.js
import { atom } from "recoil";
export const searchState = atom({
key: "searchStateKey",
default: "",
});
To read and write to this atom we use useRecoilState
.
import { useRecoilState } from "recoil";
import { searchState } from './store';
export default () => {
const [search, setSearch] = useRecoilState(searchState);
return (
<input
value={search}
onChange={(e) => {
setSearch(e.target.value);
}}
/>
};
Our application need to handle a combination of state (derived state), and that's when we use a selector. In our recipe application we use a selector to return a filtered list of our recipes. Selectors are also memoized, making sure our performance is maintained.
import { selector } from "recoil";
export const filteredRecipesState = selector({
key: "filteredRecipes",
get: async ({ get }) => {
const recipes = await fetchRecipes(); // async
const searchValue = get(searchState); // sync
return recipes.filter((r) => r.name.indexOf(searchValue) >= 0);
},
});
We can use the same useRecoilValue()
on selectors as we do on atoms. The cool thing here is that if our search state changes, our selector state will trigger a change as well.
const { useRecoilValue } from 'recoil';
const { filteredItemsState } from './store';
const Recipes = () => {
const filteredItems = useRecoilValue(filteredItemsState);
...
};
Finally we need to add some spinners with React Suspense, which is supported out of the box.
<ErrorBoundery>
<Suspense fallback={<span>Loading..</span>}>
<Recipes />
</Suspense>
</ErrorBoundery>
Take a look at github to see all the code, which is also written in TypeScript. 😁
Libraries for React are constantly appearing, but Recoil.js is much more than “just another library”. Compared to other state libraries for React, Recoil feels like a fresh breath from the future - and it's great in so many ways:
Recoil is made specifically for React and offers close compatibility with features like React Concurrent mode and React Suspense as we saw in the example. The team also claims to support new React features as they become available.
When I tried Recoil I realized how complicated and difficult other state libraries like Redux were.
Recoil offers a simple API, with semantics and behavior in a known reactish manner. It also comes "boilerplate-free". All you need to get started is to wrap your code with RecoilRoot.
import { RecoilRoot } from "recoil";
ReactDOM.render(
<RecoilRoot> ⬅️
<App />
</RecoilRoot>,⬅️
document.getElementById("root")
);
The concept of atoms let us isolate state in small pieces, making it very flexible. By using selectors we can easily combine pieces of state and move between being synchronous and asynchronous without modifying the components. It's also easy to observe and/or persist the entire state - which use be used to synchronize our state with a server or debug our app with time travel.
Read more about the motivation for Recoil here.
The concept of derived state is very powerful, and atoms and selectors let you build a flexible and maintainable application. Its super easy to get started, and it really feels like a natural extension of React. It might not be a silver bullet, but if you need a way to handle distributed state in a simple manner - Recoil is your friend. 👍
Recoil is still in alpha and parts of the API might change, but based on the fact that Recoil is created by a Facebook team and currenty has 10k stars on github - I would say it’s pretty safe to try out. 😄
I recommend watching Dave McCabe's presentation of Recoil. He explains why they made Recoil and demonstrates the power of atoms and selectors.
Loading…