As browser-based clients have grown in complexity in recent years they have become a far cry from the simple static HTML skeletons of old. To accomodate the increasing data demands, sophisticated operations, and interactivity of modern UIs, many crafty frontend libraries have emerged in the past decade. Among the most popular of these is react.js. As the complexity of UIs has grown, efficient application state management to deal with all the data changes has become a crucial feature of scalable frontend infrastructures. Several popular state management libraries have come to the forefront such as redux and mobx. While these libraries have various advantages and drawbacks, they are marred by a certain lack of parallelism with the UI libraries they interface with. As anyone who has worked with redux can attest, as useful as it is it sticks out like a sore thumb in comparison to the rest of the tooling and involves the use of much tedious configuration and boilerplate in order to extend it even marginally. Perhaps what we need is manageable state management.
Happily, Facebook Engineering has recently released recoil.js, a state management module that leverages react-like concepts that mesh with the overall design philosophy of react. This strikes me as a very cool idea. So let’s learn something about it!
First and foremost, what problems does recoil address to justify yet another state management tool? In short it provides a clean and intuitive interface for shared state between components, derived data and queries, and observation. We’ll address these in turn and then take a dive into the main concepts and syntax of recoil.
Any state management library obviously wants to solve the problem of sharing state application-wide. The cool thing about recoil is that it allows for components to tap into a store without much boilerplate or without imposing unreact-like concepts onto your components.
Derived data and queries are of great use when you want components to tap into certain regular computations or API requests. Clearly if many components are going to be doing the same thing, it makes sense to host this functionality outside the component and to provide a subscribable set of functions called selectors to handle this need.
Observation is a useful feature to have when dealing with application state. In essence, observation allows a component to watch everything that’s happening in the app. This is useful for debugging, logging, persistence and keeping components’ state synchronized.
One of the attractive aspects of recoil is its comparative simplicity. There are really only two main concepts to it, atoms and selectors. Let’s go over the basics.
Atoms are the changeable pieces of application state that various components throughout the app can subscribe to. They account for the “single source of truth” principle of state management. When an atom updates, every component subscribed to it re-renders and syncs with the current state of the atom. Creating an atom is easy:
import { atom } from 'recoil';
const counterState = atom({
key: ‘counterState’,
default: 0
});
That’s really all there is to it. You define a variable using the atom() function, and pass it an object with a key and a default state. Then it’s just a matter of subscribing the desired component to that atom, which can be achieved with precisely zero configuration. Using hooks, doing so looks like this:
const App = () => {
const [count, setCount] = useRecoilState(counterState);
const loading = useRecoilValue(counterState);
...
}
Do the same for each component you wish to connect to the same piece of state and they will each consistently sync up with it and reflect its updates accordingly. useRecoilState specifies a setter function, so that the state can be updated within the component when called. useRecoilValue is a getter function that grabs the current state of the atom for display or general use within the component.
Aside from a few minor details that’s essentially all there is to atoms. The naming is apt; atoms are meant to be the most elementary pieces of state with little baggage besides the minimum definitional properties needed to specify it.
Next comes selectors. Selectors are a bit more complicated. Basically, they handle derived state in recoil. They accept either atoms or other selectors as input. You define a selector in a similar way as an atom:
import { selector } from 'recoil';
const checkCounterState = selector({
key: ‘counterState’,
get: ({ get } ) => {
const count = get(counterState)
function isPrime(num) {
for(var i = 2; i < num; i++)
if(num % i === 0) return false;
return num > 1;
}
return isPrime(count);
})
This selector tells you if the current state of the counter is a prime number. You can subscribe to the selector within any component and run this computation wherever needed. Selectors provide a consistent app-wide API for calculating derived state. Selectors can also be writable, meaning you can update the state using them. It also comes with async support without the need of any external libraries, so selectors can return promises and be used for server queries.
While there is more depth to recoil, what you see here is the core of the library. Recoil is in its infancy, and is even considered merely “experimental” by its developers. Few however can deny the appeal of its clean and simple interface. Recoil is certainly a piece of state you will want to subscribe to as it matures!
Top comments (0)