React developers have convinced themselves that managing state is rocket science. We've built entire careers around Redux boilerplate and Context API "best practices." But here's the thing: you're probably overthinking it.
Let me rank the popular approaches from "actually good" to "why are you doing this to yourself":
Your URL is the most underrated state management tool. It's persistent, shareable, and works with the back button. Revolutionary stuff.
// Instead of this Redux ceremony:
const dispatch = useDispatch();
const filters = useSelector((state) => state.filters);
// Just do this:
const searchParams = useSearchParams();
const category = searchParams.get("category") || "all";
// Yeah, yeah I know, Next.js...
Why it's amazing:
"But the Udemy course that I got from my free company account says that props drilling is bad!" - Developer who's never actually tried it
// This is supposedly "terrible":
function App() {
const [user, setUser] = useState(null);
return <Header user={user} onLogout={() => setUser(null)} />;
}
function Header({ user, onLogout }) {
return <UserMenu user={user} onLogout={onLogout} />;
}
What's actually wrong with this? Nothing. It's:
Props drilling becomes a problem at 6+ levels deep. Until then, it's just... props. Thinking more about it - even then it's fine. I think I'm in love with props.
When you actually need global state, these are solid:
// Zustand - Redux without the ceremony
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
// Jotai - Atomic and clean
const countAtom = atom(0);
const [count, setCount] = useAtom(countAtom);
Context is for theming and auth - stuff that rarely changes. Using it for frequently updating state is like using a sledgehammer to crack an egg.
// This will re-render your entire app:
const AppContext = createContext();
const [state, setState] = useState({ user, cart, filters, theme });
Use Context only when:
Redux is what happens when you take a simple problem and make it enterprise-grade complicated.
// Want to toggle a boolean? Here's your 47-step process:
const TOGGLE_MODAL = "TOGGLE_MODAL";
const toggleModal = () => ({ type: TOGGLE_MODAL });
const modalReducer = (state = false, action) => {
switch (action.type) {
case TOGGLE_MODAL:
return !state;
default:
return state;
}
};
// ... 200 more lines of boilerplate
Only use Redux if:
PS: I write my Redux code deliberately worse than I'm supposed to, so you're easier to convince that this approach is wrong ;). What are you gonna do, report me to the Redux police? It's my blog, I'll strawman Redux all I want.
Here's my foolproof method for choosing state management:
If your state affects what the user sees and should be shareable/bookmarkable, put it in the URL.
Use props drilling. Seriously. It's not that bad.
Use SWR or React Query. Don't reinvent caching.
Fine, use Zustand or Jotai. But ask yourself twice.
My condolences. At least you're getting paid.
"Props drilling is unmaintainable!" You're drilling through 8 components. That's not a props problem, that's an architecture problem. Refactor your components.
"URL state is ugly!" So is your Redux boilerplate. At least URLs are functional.
"What about performance?" Props drilling is faster than Context re-renders. URL updates are faster than Redux dispatches. You're optimizing the wrong thing.
"My team won't understand it!" Your team understands URLs and function parameters. If they don't understand props drilling, maybe the problem isn't the pattern.
Stop overthinking state management. Most React apps need exactly zero state management libraries. The web platform + React's built-in tools handle 90% of use cases.
Your app doesn't need to be enterprise-grade. It needs to work.