Back home

Use query params to manage your application state

When building web applications, managing state is a crucial aspect of creating a smooth user experience. So let's dive in two similar but highly different approachs!

The Traditional Approach: Using useState

Typically, when you need to filter data in a table you might use React's useState hook to store the filters. Let's look at an example:

import React, { useState } from 'react';

export default function Page() {
  const [filters, setFilters] = useState({
    name: "",
    email: "",
  });

  const handleSaveFilters = () => {
    // Do your thing
    console.log("filters", filters);
  }

  return (
    <div>
      <input
        type="text"
        value={filters.name}
        onChange={(event) =>
          setFilters((old) => ({
            ...old,
            name: event.target.value,
          }))
        }
      />
      <input
        type="text"
        value={filters.email}
        onChange={(event) =>
          setFilters((old) => ({
            ...old,
            email: event.target.value,
          }))
        }
      />
      <button type="submit" onClick={handleSaveFilters}>Save</button>
    </div>
  );
}

While this approach works, it has some limitations. The state is not persisted between page reloads, and it's not easily shareable or bookmarkable. If you want to add those capabilities, well...more work to do. Lets move to the alternative!

Embracing Query Params with React Router v6

Now, let's see how we can improve this using query parameters. React Router v6 makes this process straightforward with its useSearchParams hook.

Here's the same example refactored to use query params:

import { useSearchParams } from 'react-router-dom';

export default function Page() {
  const [searchParams, setSearchParams] = useSearchParams();

  const handleSaveFilters = () => {
    // Filters are already saved in the URL
    console.log("filters", Object.fromEntries(searchParams));
  }

  return (
    <div>
      <input
        type="text"
        value={searchParams.get('name') || ''}
        onChange={(event) => setSearchParams(params => {
          params.set('name', event.target.value);
          return params;
        })}
      />
      <input
        type="text"
        value={searchParams.get('email') || ''}
        onChange={(event) => setSearchParams(params => {
          params.set('email', event.target.value);
          return params;
        })}
      />
      <button type="submit" onClick={handleSaveFilters}>Save</button>
    </div>
  );
}

Looks good! It implies going back to basics like old times and make a mindset twist.

Advantages of Using Query Params

  1. URL-based State: The state is maintained in the URL, allowing easy sharing and bookmarking of the current application state.

  2. Persistence: Filters persist even when the page is reloaded, as they're part of the URL.

  3. Simplified Navigation: Users can use browser back and forward buttons to move between different filter states.

  4. SEO-friendly: URLs with search parameters are more search engine friendly.

  5. Reduced Code Complexity: No need to manage a separate local state for filters, simplifying your code.

  6. Automatic Synchronization: React Router handles keeping the URL parameters in sync with your UI.

  7. Easy Debugging: You can see the current state of your application just by looking at the URL.

  8. Stateless HTTP: This approach aligns well with the stateless nature of HTTP, creating a more robust and maintainable application.