Prevent NextJS Category Resets with useSWR and State Management
Prevent NextJS Category Resets with useSWR and State Management

Handling Nested Categories in NextJS with useSWR: Fixing State and Re-Renders

Fix NextJS nested category resets with useSWR by preventing default link behaviors and proper React state management.7 min


Handling nested categories in NextJS using useSWR can be tricky, especially when managing state and preventing unnecessary re-renders. Recently, developers have reported facing frustrating issues: Clicking on a category triggers a re-render that resets their selected category—usually reverting the state to null. If you’ve ever experienced this behavior, you’re not alone.

Typically, you’d fetch categories from an API and dynamically display them as clickable links. When a category is clicked, the expectation is seamless: sub-categories should load without the page suddenly resetting state, causing confusion for the end user.

Let’s break down clearly why this happens, explore useSWR and NextJS, troubleshoot together, and implement a reliable fix.

NextJS and useSWR in a Nutshell

NextJS is a popular, production-ready React framework that simplifies building performant and scalable web applications. Developers use NextJS for its server-side rendering (SSR), automatic code splitting, smart routing, and optimizations.

Data-fetching in NextJS often involves server-rendered props (getServerSideProps or getStaticProps), but for client-side fetching, useSWR is recommended. useSWR is a React hook created by Vercel that simplifies fetching, caching, and synchronizing data inside components.

With useSWR, data fetching feels intuitive and declarative rather than imperative. For example, fetching categories becomes straightforward:

import useSWR from "swr";

const fetcher = (url) => fetch(url).then(res => res.json());

function Categories() {
  const { data, error } = useSWR("/api/categories", fetcher);

  if (error) return <div>Failed to load categories</div>;
  if (!data) return <div>Loading...</div>;

  return (
    <ul>
      {data.categories.map(category => (
        <li key={category.id}><a href={`/category/${category.id}`}>{category.name}</a></li>
      ))}
    </ul>
  );
}

However, issues surface when we try to dynamically fetch sub-categories based on a selected categoryId managed with useState. Let’s dig into why.

Identifying Why Your Component Re-Renders and Resets State

Typically, your nested-category setup might look like this initially:

function NestedCategories() {
  const [categoryId, setCategoryId] = useState(null); 
  const { data, error } = useSWR(
    categoryId ? `/api/categories/${categoryId}` : null,
    fetcher
  );

  const handleCategoryClick = (id) => {
    setCategoryId(id);
  };

  return (
    <div>
      {data && data.categories ? (
        <ul>
          {data.categories.map(category => (
            <li key={category.id}>
              <a href="#" onClick={() => handleCategoryClick(category.id)}> 
                {category.name}
              </a>
            </li>
          ))}
        </ul>
      ) : 'Select a category'}
    </div>
  );
}

This looks straightforward, yet your state is resetting to null after click. Why?

In React applications—especially NextJS—clicking on links that don’t prevent default browser behavior will typically trigger a page refresh or re-render. The culprit here is often the anchor tag (<a href="#">), as “#” links cause browser jumps and reloads, resetting your React components’ state.

Another reason might be using state hooks incorrectly: If your component remounts or if your code triggers a prop change higher in your NextJS structure, the states reset without preserving your previously selected information.

Approaches You Might Have Tried (and Why They Didn’t Work)

One common suggestion developers often attempt is to replace useState with useRef to persist ID references. However, using useRef doesn’t trigger a render when its value changes, meaning your UI won’t correctly reflect category updates since state-dependent logic won’t run.

Simply put:

  • useState triggers re-renders, keeping UI in sync (but requires proper state management to prevent unwanted resets).
  • useRef persists the value but doesn’t trigger UI updates, which won’t solve the UI refresh issue effectively here.

Instead, you need a proper way to handle click events and URLs that prevents unwanted refresh behavior.

The Fix: Proper Click Handling & State Management

Here’s a reliable way to fix this issue:

  1. Avoid using “#” as links without event.preventDefault().
  2. Explicitly prevent the default browser link click behavior in your click handler.
  3. Update your state correctly, triggering useSWR fetches without full page reloads.

Here’s an improved, working example:

import { useState } from "react";
import useSWR from "swr";

const fetcher = (url) => fetch(url).then(res => res.json());

function NestedCategories() {
  const [categoryId, setCategoryId] = useState(null);
  
  const { data, error } = useSWR(
    categoryId ? `/api/categories/${categoryId}` : '/api/categories',
    fetcher
  );

  const handleCategoryClick = (id, event) => {
    event.preventDefault(); // prevent re-render caused by default link behavior
    setCategoryId(id);
  };

  if (error) return <div>Error loading data.</div>;
  if (!data) return <div>Loading categories...</div>;

  return (
    <ul>
      {data.categories.map(category => (
        <li key={category.id}>
          <a href={`/category/${category.id}`} onClick={(e) => handleCategoryClick(category.id, e)}> 
            {category.name}
          </a>
        </li>
      ))}
    </ul>
  );
}

Crucially, the “href={`/category/${category.id}`}” provides proper URL structure, while “event.preventDefault()” avoids causing unwanted refreshes. Now your updates stay localized and React-friendly. Test it out: you’ll see the proper fetching of sub-categories without the frustrating reset to null.

Best Practices Working With Nested Data in NextJS

Optimizing your state management when dealing with nested categories can save you headaches:

  • Fetch initial high-level categories once via useSWR; lazy load sub-categories contextually only upon user interaction (i.e., on click).
  • Prevent default browser behaviors explicitly in click handlers to avoid unwanted re-rendering issues.
  • Leverage caching and deduplication features provided by useSWR; minimize unnecessary repeated network requests.
  • Consider flat structures with interconnected keys if nesting becomes complex. You can flatten hierarchical data for easier maintenance and rendering.
  • Use debugging tools like NextJS debugging or React Developer Tools proactively to understand component behavior clearly.

For more tips on optimizing React application performance, you might also want to read our previous post on performance optimization in JavaScript applications.

Wrapping Up

Handling nested categories in NextJS doesn’t need to be complex. By recognizing the cause of the re-rendering issue—browser default link behavior combined with React state management—and correcting it through proper event handling and state updates, we can confidently build smooth user experiences and cleaner, maintainable code.

Remember: preventing default browser re-rendering is often as simple as event.preventDefault() in your click handler. Pairing that tip with proper useSWR state management clears up confusing state resets brilliantly.

Have you faced similar category nesting issues in NextJS? How did you resolve them—or do you still have questions we didn’t address yet? Share your thoughts or ask away!


Like it? Share with your friends!

Shivateja Keerthi
Hey there! I'm Shivateja Keerthi, a full-stack developer who loves diving deep into code, fixing tricky bugs, and figuring out why things break. I mainly work with JavaScript and Python, and I enjoy sharing everything I learn - especially about debugging, troubleshooting errors, and making development smoother. If you've ever struggled with weird bugs or just want to get better at coding, you're in the right place. Through my blog, I share tips, solutions, and insights to help you code smarter and debug faster. Let’s make coding less frustrating and more fun! My LinkedIn Follow Me on X

0 Comments

Your email address will not be published. Required fields are marked *