Jotai News: Building High-Performance React Apps with Atomic State and React Query

In the ever-evolving landscape of React development, the conversation around state management is perpetual. While powerful libraries like Redux have long dominated, a new wave of minimalist, atomic solutions is reshaping how we think about application state. Among these, Jotai has emerged as a favorite for its simplicity, flexibility, and performance. However, managing client-side UI state is only half the battle. Modern applications are heavily reliant on server state—data fetched from APIs that has its own lifecycle of caching, synchronization, and invalidation.

This is where the true power of a modern stack is revealed: combining a nimble client state library like Jotai with a dedicated server state powerhouse like TanStack’s React Query. By leveraging each tool for what it does best, we can build applications that are not only highly performant and scalable but also a joy to develop and maintain. This article provides a comprehensive deep dive into this powerful combination. We’ll explore the core concepts, walk through practical implementation by building a “news feed” application, and cover advanced techniques and best practices to help you master this modern approach to React state management. This pattern is a game-changer for any React News or data-driven application you’re building.

Understanding the Core Concepts: Atoms and Server State

Before diving into code, it’s crucial to understand the philosophical separation of concerns that makes this architectural pattern so effective. We are intentionally splitting our application’s state into two distinct categories: client state managed by Jotai and server state managed by React Query.

Keywords: React code on screen - How to store and reuse your code snippets?
Keywords: React code on screen – How to store and reuse your code snippets?

Jotai’s Atomic Model: Simplicity and Precision

Jotai’s philosophy is rooted in atomicity. Instead of a single, monolithic store (a common pattern in Redux News apps), Jotai breaks state down into individual, independent pieces called “atoms.” An atom is the smallest possible unit of state. It can hold a primitive value, an object, or an array. React components can subscribe to one or more atoms, and crucially, they will only re-render when an atom they subscribe to changes. This bottom-up approach provides automatic performance optimization out of the box, preventing the “waterfall” re-renders that can plague applications with large, interconnected state objects.

Here’s a basic example of defining an atom to hold a search query for our news app:

import { atom } from 'jotai';

// An atom to store the user's search term.
// It's a simple, writable atom with an initial value of an empty string.
export const searchQueryAtom = atom('');

// A derived, read-only atom that checks if the search bar has text.
export const hasSearchQueryAtom = atom(
  (get) => get(searchQueryAtom).length > 0
);

In this snippet, searchQueryAtom is a primitive piece of state. Any component using this atom with the useAtom hook will re-render when its value changes. hasSearchQueryAtom is a derived atom; it depends on another atom and recalculates its value automatically, providing a powerful way to compose state without extra boilerplate. This is a significant departure from the selector-based models of libraries like Recoil News or Zustand News.

Keywords: React code on screen - Java Final Keyword: An In-Depth Guide
Keywords: React code on screen – Java Final Keyword: An In-Depth Guide

React Query: The Missing Data-Fetching Layer

Server state is fundamentally different from client state. It’s persisted remotely, can be changed by other users, and can become stale. Trying to manage this complexity with client-state tools is a common pitfall. This is where React Query News comes in. It’s not a global state manager; it’s a server-state manager that provides declarative, hook-based tools for fetching, caching, and updating data. It expertly handles concerns like caching, request deduplication, background refetching, and stale-while-revalidate logic, which are tedious to implement manually.

Practical Implementation: Building a News Feed

Keywords: React code on screen - 5 Python and R Libraries For GIS | Life in GIS
Keywords: React code on screen – 5 Python and R Libraries For GIS | Life in GIS

Let’s apply these concepts by building a simple Hacker News-style feed. We’ll use Jotai for UI state (like the selected news category) and React Query, via the official jotai-tanstack-query integration, to fetch the news stories.

Project Setup and Provider Configuration

First, ensure you have the necessary libraries installed:

npm install jotai @tanstack/react-query jotai-tanstack-query

Next, wrap your application’s root component with the required providers. This setup is essential for both frameworks like Next.js News and Remix News, as well as client-side apps built with Vite News.

import React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider as JotaiProvider } from 'jotai';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import App from './App';

const queryClient = new QueryClient();

const container = document.getElementById('root');
const root = createRoot(container);

root.render(
  <React.StrictMode>
    <JotaiProvider>
      <QueryClientProvider client={queryClient}>
        <App />
      </QueryClientProvider>
    </JotaiProvider>
  </React.StrictMode>
);

Defining UI State and Fetching Data with `atomWithQuery`

Now, let’s define the state. We need an atom to hold the current news category (e.g., ‘top’, ‘new’, ‘best’). Then, we’ll create a query atom that depends on the category atom to fetch the corresponding list of story IDs from an API.

import { atom } from 'jotai';
import { atomWithQuery } from 'jotai-tanstack-query';

const HN_API_BASE_URL = 'https://hacker-news.firebaseio.com/v0';

// 1. Atom for UI state: the selected news category
export const categoryAtom = atom('topstories'); // 'topstories', 'newstories', etc.

// 2. Atom that uses React Query to fetch data based on the category atom
export const storyIdsAtom = atomWithQuery((get) => ({
  // The query key is dynamic and depends on the categoryAtom's value.
  // When categoryAtom changes, React Query will trigger a new fetch.
  queryKey: ['storyIds', get(categoryAtom)],
  queryFn: async () => {
    const category = get(categoryAtom);
    const response = await fetch(`${HN_API_BASE_URL}/${category}.json`);
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  },
  // Keep previous data while fetching new category for a smoother UX
  keepPreviousData: true, 
}));

The atomWithQuery utility from jotai-tanstack-query is the magic that bridges the two libraries. It creates a Jotai atom whose value is the result of a React Query operation. Notice how the queryKey and queryFn depend on the value of categoryAtom via the get function. This creates a reactive link: whenever categoryAtom is updated, storyIdsAtom automatically triggers a refetch with the new category.

Advanced Techniques: Dynamic and Derived Query Atoms

Fetching a list of IDs is just the first step. To build a functional UI, we need to fetch the details for each individual story. Fetching them all at once would be inefficient and slow. A more performant pattern is to fetch details only for the stories currently visible on the screen. This is where parameterized and derived atoms shine.

Creating a Parameterized Atom for Story Details

We can create a function that, given a story ID, returns a query atom for that specific story’s details. This pattern is similar to the `atomFamily` utility from `jotai/utils` and is incredibly powerful for managing collections of data.

import { atomWithQuery } from 'jotai-tanstack-query';

const HN_API_BASE_URL = 'https://hacker-news.firebaseio.com/v0';

// A function that returns a query atom for a specific story ID.
// This is not an atom itself, but an atom *creator*.
export const storyDetailAtom = (id) =>
  atomWithQuery(() => ({
    queryKey: ['storyDetail', id],
    queryFn: async () => {
      const response = await fetch(`${HN_API_BASE_URL}/item/${id}.json`);
      if (!response.ok) {
        throw new Error(`Failed to fetch story ${id}`);
      }
      return response.json();
    },
    // Cache data for a longer period as story details don't change often.
    staleTime: 1000 * 60 * 5, // 5 minutes
  }));

Composing a Paginated View with Derived Atoms

Now we can combine our atoms to create a paginated view. We’ll add an atom for the current page and then a derived atom that slices the list of IDs and maps them to their corresponding detail atoms.

import { atom } from 'jotai';
import { storyIdsAtom } from './storyIdsAtom'; // from previous example
import { storyDetailAtom } from './storyDetailAtom'; // from previous example

const STORIES_PER_PAGE = 20;

// Atom for managing the current page number
export const currentPageAtom = atom(0);

// A derived atom that computes the list of story detail atoms for the current page
export const paginatedStoriesAtom = atom((get) => {
  const page = get(currentPageAtom);
  const storyIdsData = get(storyIdsAtom); // This will suspend if loading

  if (storyIdsData.isLoading || !storyIdsData.data) {
    return []; // Return empty array while the IDs are loading
  }

  const startIndex = page * STORIES_PER_PAGE;
  const endIndex = startIndex + STORIES_PER_PAGE;
  const idsForPage = storyIdsData.data.slice(startIndex, endIndex);

  // Return an array of atoms, not the data itself!
  return idsForPage.map((id) => storyDetailAtom(id));
});

A component can then use paginatedStoriesAtom to get the list of detail atoms and render a `Story` component for each one. Inside the `Story` component, you would use `useAtom` with the specific `storyDetailAtom` passed via props. This architecture is incredibly performant. When a single story is upvoted and its data is refetched, only that specific `Story` component will re-render, not the entire list. This granular control is a key benefit of the atomic model and works beautifully in complex UIs, whether you’re using React Native Paper News components or building for the web with Framer Motion News for animations.

Best Practices, Optimization, and Ecosystem Integration

To get the most out of this powerful combination, it’s important to follow some established best practices and understand how it fits into the broader ecosystem, from React Native News apps built with Expo News to server-rendered sites using Gatsby News or Blitz.js News.

Key Best Practices

  • Keep Atoms Small: Adhere to the atomic principle. Each atom should represent a single, minimal piece of state.
  • Derive, Don’t Duplicate: Use derived atoms (`atom(get => …)` ) to compute values from other atoms. Avoid storing state that can be calculated from existing state.
  • Separate Concerns: Use Jotai strictly for client-side UI state (e.g., modal visibility, form state, theme). Use React Query for all server state interactions. This clear separation makes your codebase easier to reason about. For complex forms, you might even integrate a dedicated library like React Hook Form News.
  • Leverage `jotai/utils`: This package contains helpful utilities like atomWithStorage (for persisting state to localStorage), atomFamily (for creating parameterized atoms), and selectAtom (for subscribing to a slice of an atom’s state).

Optimization and Testing

The primary optimization benefit comes from Jotai’s automatic re-rendering based on atom subscriptions. This fine-grained dependency tracking minimizes component updates. For a seamless user experience, wrap your data-dependent components in React’s `<Suspense>` boundary. The atoms created with `atomWithQuery` are Suspense-ready, which simplifies handling loading states elegantly.

When it comes to testing, this architecture is highly testable. You can test your atoms and their interactions in isolation using tools like Jest News and React Testing Library News. Because the data-fetching logic is encapsulated within query atoms, you can easily mock API responses at the query level, making your component tests clean and reliable. For end-to-end testing, frameworks like Cypress News or Playwright News can validate the entire flow.

Conclusion: A Modern Stack for Modern Challenges

The combination of Jotai and React Query represents a significant step forward in React state management. By embracing the separation of client and server state, we unlock a development paradigm that is both simple and immensely powerful. Jotai provides a minimal, boilerplate-free API for managing the ephemeral state of your UI, while React Query robustly handles the complex lifecycle of asynchronous data.

The result is an application that is performant by default, with optimized re-renders and intelligent data caching. The developer experience is streamlined, allowing you to focus on building features rather than wrestling with state management intricacies. Whether you are building a complex dashboard, a mobile app with React Native News and React Navigation News, or a content-rich site with a modern framework, this powerful duo provides a scalable and maintainable foundation. The next time you start a project, consider leaving the monolithic stores behind and embrace the focused, composable power of Jotai and React Query.