BLOG

Next.js 14: The Power of URL in State Management

#d illustration of a woman coding surrounded by holograms

Mauro Davoli

Frontend Engineer

Nov 24, 2023

5 min read

Next.js

Tools

Frontend

In the ever-evolving landscape of web development, a silent hero has emerged — the URL. As frontenders navigate the challenges of integrating a state manager into server components, the URL, provides a welcome solution in Next.js development.

Beyond its traditional role as a web address, the URL has transformed into a powerful state management tool, revolutionizing how users interact with and save their online experiences.

This method shows how the URL can do a bunch of things, giving power to both users and developers to:

  • share their curated content with a simple link.
  • bookmark pages for future reference
  • seamlessly save search results

Let’s apply this concept with a practical example in a server component.


import Link from 'next/link';

export default function ProductPage({
  searchParams,
}: {
  searchParams: { [key: string]: string | string[] | undefined };
}) {
  const selectedColor = (searchParams?.color || 'blue') as string;
  const selectedSize = (searchParams?.size || 'M') as string;

  const sizeVariants = ['S', 'M', 'L', 'XL'];

  return (
    // ...
    <div>
      {sizeVariants.map((size, index) => (
        <Link
          key={index}
          href={`?${new URLSearchParams({
            color: selectedColor,
            size,
          })}`}
          className={`${
            selectedSize === size ? 'bg-blue-500' : 'bg-gray-200'
          } mr-2 inline-block h-8 w-8 text-center leading-8`}
        >
          {size}
        </Link>
      ))}
    </div>
    // ...
  );
}

This component accepts a prop named searchParams, which is an object containing key-value pairs representing the parameters from the URL query string. The selectedColor and selectedSize variables are extracted from these parameters.

Suppose you have a product page for a pink shirt in size ‘XL’. The URL might look like this:


https://www.examplestore.com/product-page?color=pink&size=XL

Now, let’s break down how the code contributes to this:

1 — Default Values:

In essence, default values act as a safety net, ensuring that the component gracefully handles diverse scenarios and remains robust in the face of potential irregularities in the data it receives.


const selectedColor = (searchParams?.color || 'blue') as string;
const selectedSize = (searchParams?.size || 'M') as string;

2 — Link Generation:

This line constructs the href attribute for each link, creating a new URLSearchParams object with the selected color and the specific size.

By incorporating new URLSearchParams, developers can create URLs that adhere to standards, ensuring compatibility across different browsers


href={`?${new URLSearchParams({ color: selectedColor, size })}`}

3 — Styling:

The class names dynamically style the links, highlighting the selected size with a blue background and providing a visual cue.


className={`${
  selectedSize === size ? 'bg-blue-500' : 'bg-gray-200'
} mr-2 inline-block h-8 w-8 text-center leading-8`}

Let’s explore an example on the client side featuring a search input.


'use client';

import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { useSearchParams, usePathname, useRouter } from 'next/navigation';
 
export default function Search() {
  const searchParams = useSearchParams();
  const pathname = usePathname();
  const { replace } = useRouter();
 
  function handleSearch(term: string) {
    const params = new URLSearchParams(searchParams);
    if (term) {
      params.set('query', term);
    } else {
      params.delete('query');
    }
    replace(`${pathname}?${params.toString()}`);
  }
}


return (
    <div className="relative flex flex-1 flex-shrink-0">
      <label htmlFor="search" className="sr-only">
        Search
      </label>
      <input
        className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
        placeholder={placeholder}
        defaultValue={searchParams.get('query')?.toString()}
        onChange={(e) => {
          handleSearch(e.target.value);
        }}
      />
      <MagnifyingGlassIcon className="absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
    </div>
  );
}

These lines use Next.js 14 client-side navigation hooks. useSearchParams retrieves the current URL parameters, usePathname gets the current pathname, and useRouter provides routing functionalities. Even if you are in client-side and can use hooks like useState, we continue in the same line of the benefits that using URL as a state manager provides.

1 —handleSearch:

The handleSearch function updates the URL parameters based on the provided search term. It utilizes a URLSearchParams object to manage the parameters, setting or deleting the 'query' parameter based on whether a term is provided. Finally, it uses replace to update the URL.


function handleSearch(term: string) {
    const params = new URLSearchParams(searchParams);
    if (term) {
      params.set('query', term);
    } else {
      params.delete('query');
    }
    replace(`${pathname}?${params.toString()}`);
  }

2 — defaultValue:

searchParams.get('query')?.toString() retrieves the value of the 'query' parameter from the URL. If the parameter exists, it returns the value; otherwise, it returns null.


<input
        className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
        placeholder={placeholder}
        defaultValue={searchParams.get('query')?.toString()}
        onChange={(e) => {
          handleSearch(e.target.value);
        }}
      />

Sharing Curated Experiences with Ease:

One of the most compelling aspects of using the URL as a state manager is the seamless ability to share curated experiences effortlessly. Imagine a user exploring an online store’s vibrant collection of XL-sized, pink-hued products. With the URL serving as the state manager, the experience becomes dynamic and easily shareable.

Take, for instance, the URL: https://www.examplestore.com/product-page?color=pink&size=XL. This link encapsulates a specific state within the application – in this case, showcasing products tailored to the user's preference for pink color and XL size. The magic happens when this link is shared.

Here’s a list of examples showcasing how the URL can serve as an ideal state manager, particularly in the context of server components:

Pagination: /products?page=2

Search Inputs: /search?query=shoes&type=sneaker

Sorting Options: /products?category=clothing&sort=price-asc

Filtering by Category: /products?category=electronics

User Preferences: /profile?theme=dark&language=en

Dynamic Forms: /form?step=2

Conclusion

The examples showcased here represent just the tip of the iceberg when it comes to leveraging the URL as a powerful state manager in Next.js 14 server and client components. The beauty lies in the simplicity and flexibility of this approach, offering developers a canvas on which they can unleash their creativity.

Mauro Davoli

Frontend Engineer

Nov 24, 2023

5 min read

Next.js

Tools

Frontend

BLOG

Unlock forbidden knowledge

Explore more

Building cool search UIs with Algolia and InstantSearch

Frontend

Technology

Guides

Building cool search UIs with Algolia and InstantSearch

In this article, we’re going to pick some characters from a video game (Genshin Impact) and display them in a fancy way using the aforementioned programs.

Pablo Haller

Tropical.rb 2024 Recap

Conference

Backend

Tropical.rb 2024 Recap: My Experience

It’s been two months since (time flies!) but I couldn’t resist sharing my first-time experience at Tropical.rb, the largest Ruby on Rails event in Latin America.

Franco Pariani

Franco Pariania and Nicolas Erlichman. Gogrow

Business

Welcoming a New Era: Introducing Our New Co-CEO at GoGrow

We’re thrilled to share a significant update to GoGrow’s leadership. Nicolas Erlichman is joining Franco Pariani as co-CEO.

GoGrow