Maximizing React State Efficiency: A Deep Dive into Recoil Selectors vs. useMemo for Optimal Performance

me_akash_dash
6 min readMar 30, 2024

In this blog, we will see how to implement the recoil state in a dynamic web page optimally.

React and Recoil

Let's take a feature to show how LinkedIn manages different notification counts. For example

It could use the “useState” hook to store different notification counts. However, it can optimally be implemented by the state management because the Navbar is present on all the pages.

I use Recoil because it's easy to use and has built-in support for asynchronous data flow and dependency tracking. Unlike many other state management libraries, Recoil allows components to subscribe to atom and selector state changes without needing to manage complex subscription patterns or deal with stale data issues. This enables more efficient and predictable handling of asynchronous data updates, resulting in cleaner and more maintainable code.

Recoil has atoms and selectors. Atoms in recoil are the smallest structure and have one key which is unique and a default value. Selectors are the value that depends upon atoms. We are going to implement the below architecture.

Architecture

In this architecture there are 3 atoms means three variables that store different types of notification counts(network, job, message) and another selector totalCount which is the combination of these 3 individual atoms.

For implementation start a fresh react project using Vite

  1. Start a new React project using the below command
npm create vite@latest

Clear the index.css and App.css file for a fresh start

Run these below commands one by one

npm install
npm install recoil

Now clean all the unnecessary code in App.jsx. And after cleaning your file it looked like this

Run the react app using the below command

npm run dev

2. Create a wireframe of the navbar.

Now paste this code into your App.jsx

import './App.css'

function App() {

return (
<>
<Navbar />
</>
)
}

function Navbar(){
return(
<>
<button>Home</button>

<button>My network (8)</button>
<button>Jobs (10)</button>
<button>Messaging (12)</button>

<button>Total Count(30)</button>

<button>Me</button>
</>
)
}

export default App

After successfully running your app you can see on the http://localhost:5143 all the navbar components with their hard-coded counts. The page looks like this

3. Create recoil state variables.

Now create a file inside the “src” folder named atom.js in which we can store all the state variables.

//atom.js

import { atom, selector } from "recoil";

export const networkAtom = atom({
key: "networkAtom",
default: 0
});

export const jobsAtom = atom({
key: "jobsAtom",
default: 0
});

export const messagingAtom = atom({
key: "messagingAtom",
default: 0
});

Here we have all the key and their default values.

4. Add all recoil state variables into the App.jsx.

Use “useRecoilValue” to only fetch the state variables inside App.jsx using their keys. Now your App.jsx is looking like this

import { RecoilRoot, useRecoilValue } from 'recoil'
import './App.css'
import { jobsAtom, messagingAtom, networkAtom } from './atom';

function App() {

return (
<RecoilRoot>
<Navbar />
</RecoilRoot>
)
}

function Navbar(){
const networkCount=useRecoilValue(networkAtom);
const jobCount=useRecoilValue(jobsAtom);
const messageCount=useRecoilValue(messagingAtom);


return(
<>
<button>Home</button>

<button>My network ({networkCount})</button>
<button>Jobs ({jobCount})</button>
<button>Messaging ({messageCount})</button>

<button>Total Count(30)</button>

<button>Me</button>
</>
)
}

export default App

5. Calculating the “totalCount” using the other 3 state variables.

This is the Naive approach means, I know that my “totalCount” variable depends upon networkCount, jobCount, and messageCount. If any of the three values changes I have to re-calculate the totalCount again. So I use the “useMemo” hook to optimally calculate.

Now the App.jsx code is looking like this

import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil'
import './App.css'
import { jobsAtom, messagingAtom, networkAtom } from './atom';
import { useMemo } from 'react';

function App() {

return (
<RecoilRoot>
<Navbar />
</RecoilRoot>
)
}

function Navbar(){
const networkCount=useRecoilValue(networkAtom);
const jobCount=useRecoilValue(jobsAtom);
const messageCount=useRecoilValue(messagingAtom);

const totalNotificationCount = useMemo(() => {
return networkCount + jobCount + messageCount;
}, [networkCount, jobCount, messageCount])

return(
<>
<button>Home</button>

<button>My network ({networkCount})</button>
<button>Jobs ({jobCount})</button>
<button>Messaging ({messageCount})</button>

<button>Total Count({totalNotificationCount})</button>

<button>Me</button>

<CustomButton />
</>
)
}

function CustomButton() {
const setNetworkCount = useSetRecoilState(networkAtom);
const setJobCount = useSetRecoilState(jobsAtom);
const setMessageCount = useSetRecoilState(messagingAtom);

const increaseCounts = () => {
setNetworkCount((prevCount) => prevCount + 1);
setJobCount((prevCount) => prevCount + 1);
setMessageCount((prevCount) => prevCount + 1);
};

return (
<button onClick={increaseCounts}>
Increase Counts
</button>
);
}

export default App

I have added a new CustomButton component which increases the 3 counts value in one click. Use the “useSetRecoilState” method to only get the setter function.

This method is valid but we can optimize this code by using “selector” in recoil. Here are some advantages of “selector” over “useMemo”.

  1. Abstraction and Encapsulation: By encapsulating the logic for computing the total count within a selector in “atom.js”, you abstract away the implementation details from the components that use these counts. This can lead to cleaner and more modular code, as components don't need to know how the total count is computed.
  2. Reusability: If the logic for computing the total count is needed in multiple components, defining it once in a selector allows you to reuse that logic across different parts of your application without duplicating code.
  3. Memoization: Recoil selectors automatically handle memoization, ensuring that the total count is only recomputed when its dependencies (networkCount, jobCount, messageCount) change. This can help optimize performance by avoiding unnecessary re-renders.

Here is the implementation

//atom.js

import { atom, selector } from "recoil";

export const networkAtom = atom({
key: "networkAtom",
default: 0
});

export const jobsAtom = atom({
key: "jobsAtom",
default: 0
});

export const messagingAtom = atom({
key: "messagingAtom",
default: 0
});

export const totalNotificationSelector = selector({
key: "totalNotificationSelector",
get: ({get}) => {
const networkAtomCount = get(networkAtom);
const jobsAtomCount = get(jobsAtom);
const notificationsAtomCount = get(notificationsAtom);
const messagingAtomCount = get(messagingAtom);
return networkAtomCount + jobsAtomCount + notificationsAtomCount + messagingAtomCount
}
})

I added a selector function which has a key and another get function which fetches all the state values using their key and does some operations. We can get this “totalCount” value in App.jsx in this way.

//App.jsx
import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil'
import './App.css'
import { jobsAtom, messagingAtom, networkAtom, totalNotificationSelector } from './atom';
import { useMemo } from 'react';

function App() {

return (
<RecoilRoot>
<Navbar />
</RecoilRoot>
)
}

function Navbar(){
const networkCount=useRecoilValue(networkAtom);
const jobCount=useRecoilValue(jobsAtom);
const messageCount=useRecoilValue(messagingAtom);
const totalNotificationCount = useRecoilValue(totalNotificationSelector);

// const totalNotificationCount = useMemo(() => {
// return networkCount + jobCount + messageCount;
// }, [networkCount, jobCount, messageCount])

return(
<>
<button>Home</button>

<button>My network ({networkCount})</button>
<button>Jobs ({jobCount})</button>
<button>Messaging ({messageCount})</button>

<button>Total Count({totalNotificationCount})</button>

<button>Me</button>

<CustomButton />
</>
)
}

function CustomButton() {
const setNetworkCount = useSetRecoilState(networkAtom);
const setJobCount = useSetRecoilState(jobsAtom);
const setMessageCount = useSetRecoilState(messagingAtom);

const increaseCounts = () => {
setNetworkCount((prevCount) => prevCount + 1);
setJobCount((prevCount) => prevCount + 1);
setMessageCount((prevCount) => prevCount + 1);
};

return (
<button onClick={increaseCounts}>
Increase Counts
</button>
);
}

export default App

Output:-

Hope this blog helps you to go through the basics of recoil and its use case. But in the next part, I will be going to show you how to update the state variable using asynchronous backend calls.

Post your thoughts about this approach and follow me for more such content.

Happy Coding :)

--

--