Back after 2 weeks of vacation!(it was actually a continuous battle with so many evaluations and exams at college). During this time I’ve been covering State Management with context API and Recoil! Let’s get right into it!
Topics Covered✅
- Context API
- Problems with Context API
- Recoil and why it’s better
- Atoms & Selectors
- Recoil hooks (
useRecoilState,useRecoilValue,useSetRecoilState) - Using
RecoilRoot - When to still use
useState
Last week, we spoke about prop drilling and how passing props through multiple layers can make your code look messy. The Context API is React’s built-in solution to this.
It lets you create global-ish state without prop drilling.
Here’s a tiny example:
// UserContext.jsx
import { createContext } from "react";
export const UserContext = createContext(null);
// App.jsx
import React, { useState } from "react";
import { UserContext } from "./UserContext";
function App() {
const [user, setUser] = useState("Nikhil");
return (
<UserContext.Provider value={{ user, setUser }}>
<Parent />
UserContext.Provider>
);
}
export default App;
// Child.jsx
import { useContext } from "react";
import { UserContext } from "./UserContext";
function Child() {
const { user } = useContext(UserContext);
return <h2>Hello, {user}!h2>;
}
export default Child;
This appears to work well. But it does so only for small and simple apps.
As our app grows, Context API introduces a few issues:
1. Re-renders
Any value change in the context re-renders every component that consumes it, even if only one component actually needed the update.
2. Too many contexts
You often end up creating multiple contexts:
- UserContext
- ThemeContext
- NotificationContext
and so on.
This becomes hard to organise and maintain.
3. Not a full state management solution
Context wasn’t designed for big, complex global state. It’s more of a “prop drilling escape hatch”.
Recoil is a super-lightweight state management library made for React. It fixes the problems above without introducing the complexity of Redux.
Why Recoil feels so good:
-
It updates only the components that need the state.
-
You don’t need multiple providers — everything sits under one RecoilRoot.
-
It’s extremely intuitive because it feels like working with React’s useState.
-
Recoil introduces atoms, which are simply pieces of global state.
Step 1: Wrap your app in a RecoilRoot
// index.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { RecoilRoot } from "recoil";
ReactDOM.createRoot(document.getElementById("root")).render(
<RecoilRoot>
<App />
RecoilRoot>
);
Step 2:Creating an atom
Atoms store global state. We usually create them in a separate folder and export them from there and then we can use them globally.
// atoms/userAtom.jsx
import { atom } from "recoil";
export const userAtom = atom({
key: "userAtom",
default: "Nikhil",
});
Step 3:Using the globally declared states in Components
For this we use majorly three functions
- useRecoilState → like useState but global
import { useRecoilState } from "recoil";
import { userAtom } from "./atoms/userAtom";
function UserUpdater() {
const [user, setUser] = useRecoilState(userAtom);
return (
<button onClick={() => setUser("Ayu")}>
Change User
button>
);
}
- useRecoilValue → read-only access- so it basically just gives us the variable and not the function to change its value.
import { useRecoilValue } from "recoil";
import { userAtom } from "./atoms/userAtom";
function UserDisplay() {
const user = useRecoilValue(userAtom);
return <h2>Hello, {user}!h2>;
}
- useSetRecoilState → write-only access
import { useSetRecoilState } from "recoil";
import { userAtom } from "./atoms/userAtom";
function LogoutButton() {
const setUser = useSetRecoilState(userAtom);
return <button onClick={() => setUser(null)}>Logoutbutton>;
}
Selectors –
Selectors let you derive data from atoms, say computed values. This is how we’d create one.
import { selector } from "recoil";
import { userAtom } from "./userAtom";
export const capitalisedUserSelector = selector({
key: "capitalisedUserSelector",
get: ({ get }) => {
const user = get(userAtom);
return user.toUpperCase();
}
});
And to use it we do this-
import { useRecoilValue } from "recoil";
import { capitalisedUserSelector } from "./atoms/selectors";
function Display() {
const name = useRecoilValue(capitalisedUserSelector);
return <h3>{name}h3>;
}
Not at all.
If a state variable is only needed by 1–2 components, using Recoil is unnecessary.
function Counter() {
const [count, setCount] = useState(0);
return (
<>
<p>{count}p>
<button onClick={() => setCount(count + 1)}>+button>
>
);
}
No reason to make count global here. React’s local state is still perfect for component-specific logic.
We use Recoil only when:
- Many components need the same state
- Passing props becomes painful
- Context starts slowing down your UI
## Things I found interesting this week - Seeing how Recoil prevents unnecessary re-renders felt magical. The component isolation makes the app feel snappier.
- Atoms being global state and also visually being “global state” made the learning curve super smooth for me.
## Wrapping up🔄
Interesting week.
Note: While learning Recoil this week, I actually didn’t know that the library had been archived. Since I had already started, I went ahead and completed it anyway — the concepts are super useful to understand. I’ll be moving to Jotai soon since it’s lightweight, actively maintained, and follows a similar mental model to Recoil, so the transition should feel smooth.
If you have any questions or feedback, make sure to comment and let me know!
I’ll be back next week with more. Until then, stay consistent!
