So what's recoil?
- Recoil is a state management library for react created by the Facebook open-source team. (This means to use it you need to have react installed)
Okay, why do we need Recoil?
Depending on what you are building, there may come instances where using React useState, useContext API or state management libraries like Redux, MobX may not be performant.
- In this write up we shall look through some of the edge cases that recoil solves and why.
- also, all this information can be found on the Recoil documentation. - if anything is unclear, I would recommend visiting the docs. Recoil is fairly new, barely a year old so it may take some time to get used to.
Minimal and React-ish - Recoil works and thinks like react. It gives you fast and flexible shared state.
Data flow graph - Derived data and asynchronous queries are tamed with pure functions and efficient subscriptions.
Cross-app observation - Recoil allows implementing persistence, routing, time travel debugging by observing all state changes across your app, without impairing code-splitting.
Flexible shared state - the ability to have different things(components) that are in sync at different parts of the React tree, thus enhancing performance.
Derived data and queries - the ability to compute things based on changing state in a way that is robust.
App-wide state observation - persistence, logging, routing and time-travel debugging.
Okey so let's do a simple deep dive into these 3 characteristics
Assuming we have a react tree as below
The above two components have items that share common state.
If we were to use useState to share state - the approach would be to hoist the state up to the parent component.
- the drawback would be, the parent component would have too much items and a re-render on each state change hence making the application slow.
What if we use context API?
- A context state is passed directly from the provider to consumer.
- But that too may not be efficient because we don't know how many items they would be hence we'd have different state and context providers for each. Our tree would look more like this
There's nothing wrong with this approach, but if you were to insert a new provider, the react tree needs to be re-rendered.
Another drawback is compiling - which will cause issues when you may need to code-split.
Sometimes you would have data coming from third-party plugins, CMS at item component level meaning you would have to declare them at top level.
But with Recoil...
- We would have state that lies separate from app tree
- Each piece of state will have its own component re-render when it changes.
- These pieces of state are called Atoms
- Atom - is a changeable, subscribable unit of state
- RecoilRoot - provides the context for which atoms have values. Must be an ancestor of any component that uses any Recoil hooks
an Atom usually takes a unique key and a default value.
** So what if there are indefinite items which need to track their state changes?**
- Basically we would want a different atom for each item id. So we would write a function that takes the id of the item and returns an atom for the item. This way each item would have its own atom/state.
- And because we want one atom for each id, we should memoize the function so that we are just producing it the first time we see the id.
So now in the two components that render items we would replace the useState function with useRecoilState and as a value call in withId(id) and pass in item id.
You can build any data structures you want out of atoms.
- Derived data refers to data computed from state and state changes. The old way (not recoil way) would mean having an extra state that is computed from other state.
- The drawback with that would be that we would need to keep it in sync since it is dependent on other state, probably needing extra functionality and even a reducer.
In recoil we have selectors
- Recoil solves the question of how do we recompute state and efficiently, as opposed to every time anything changes?
A Selector is basically a pure function with information on what state it depends on, so that when that state changes we can recompute and re-render the necessary components.
A selector can take two functions get and set.
if only a get function is provided, the selector is read-only and returns a RecoilValueReadOnly object.
if a set is also provided, it returns a writable RecoilState object.
Recoil manages atom and selector state changes to know when to notify components subscribing to that selector to re-render.
If an object value of a selector is mutated directly it may bypass this and avoid properly notifying, subscribing components. To help detect bugs, Recoil will freeze selector value objects in development mode.
- in some cases it may be desirable to allow mutating of the objects stored in selectors that don't represent state changes. we can use dangerouslyAllowMutability to override freezing objects in development mode.
Recoil uses a snapshot object to represent immutable snapshots which are intended too standardize the API for observing, inspecting and managing global recoil state.
- this feature is useful for dev-tools, state synchronization, history navigation e.t.c.
- Recoil uses some hooks like useTransactionObserver which is notified whenever recoil state changes.
Some interesting stuff about Recoil
- The API of recoil is designed to be compatible with con-current mode - something other state management libraries don't do.
- You can create atoms for specific states and assign them to certain nodes.
How is it different from MobX?
- It is simpler
- it is potentially compatible with concurrent mode
Why are existing state management libraries not compatible with concurrent mode?
- they have external state within which updates to it are not scheduled by react whileas recoil just uses local state under the hood.
Is recoil similar to react context API or based out of it?
- the basis of recoil is multi-context
- it is like a single provider that can provide any number of consumers.