Blog hero image
react

You Might Not Need to Use Context in React

Why React Query replaces most classic Context use cases

By Coolemur
2025-05-22

You Might Not Need to Use Context in React

For years, React Context was the default answer for sharing state across your React app. But in 2025, you might be reaching for Context far less often—especially if you’re using libraries like React Query (now TanStack Query). React Query makes managing server state so easy that many classic “global state” problems disappear.

Let’s look at why you might not need Context, and how React Query handles most use cases with less boilerplate, less indirection, and better performance.


Classic Context Use Cases

Before React Query, you might reach for Context in these cases:

  • Sharing user data (profile, roles, etc.) across many components
  • Fetching and sharing lists of items (like todos, posts, etc.)
  • Global loading/error states

But context comes with trade-offs: it can cause unnecessary re-renders and requires lots of boilerplate.


React Query Solves Most Data Sharing

Let’s compare approaches with an example: fetching and sharing a list of users.

Traditional Context

// UsersContext.js
import React, { createContext, useContext, useState, useEffect } from 'react';

const UsersContext = createContext();

export function UsersProvider({ children }) {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);
    fetch('/api/users')
      .then(res => res.json())
      .then(data => setUsers(data))
      .finally(() => setLoading(false));
  }, []);

  return (
    <UsersContext.Provider value={{ users, loading }}>
      {children}
    </UsersContext.Provider>
  );
}

export const useUsers = () => useContext(UsersContext);

Then you’d wrap your app with <UsersProvider> and use the useUsers hook everywhere.


With React Query (No Context Needed)

import { useQuery } from '@tanstack/react-query';

function useUsers() {
  return useQuery(['users'], async () => {
    const res = await fetch('/api/users');
    return res.json();
  });
}

// Anywhere in your app:
function UserList() {
  const { data: users, isLoading } = useUsers();
  if (isLoading) return <p>Loading…</p>;
  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

No provider. No context. No extra re-renders—React Query caches the result and shares it across all components using the same key.


Bonus: Mutations and Updating State

React Query also handles mutations (like POST/PUT/DELETE) without context. You can update or refetch queries as needed.

import { useMutation, useQueryClient } from '@tanstack/react-query';

function useAddUser() {
  const queryClient = useQueryClient();
  return useMutation(
    async (user) => {
      await fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify(user),
      });
    },
    {
      onSuccess: () => {
        queryClient.invalidateQueries(['users']);
      },
    }
  );
}

When You Do Need Context

  • Sharing non-serializable state (like refs, event emitters, or sockets)
  • Controlling UI state across the app (like theme, modal visibility, etc.)
  • Auth flows (token storage, login/logout logic)

But for most server state and caching, React Query is all you need.


Summary

Next time you think “I need global state, I’ll use Context,” ask yourself: Could this just be a React Query hook? Most data flows (fetching, caching, updating) don’t need context. Less code, fewer bugs, better UX.

If you haven’t tried React Query yet, give it a shot—your future self (and your bundle size) will thank you.

Back