The Zustand store does not reflect changes when the URL is updated

I have a Zustand store connected to the URL. See the code snippet provided below.

import { create } from "zustand";
import { persist, StateStorage, createJSONStorage } from "zustand/middleware";

const pathStorage: StateStorage = {
  getItem: (key): string => {
    const pathname = window.location.pathname;
    const p = pathname.split("/").filter(Boolean)[3];
    return p;
  },
  setItem: (key, newValue): void => {
    const slug = JSON.parse(newValue).state.tab;
    const oldSlug = window.location.pathname.split("/").filter(Boolean)[3];
    const newUrl = `/fsd/sdet/class-structure/${slug}`;
    if (oldSlug) {
      window.history.replaceState(null, "", newUrl);
    } else {
      window.history.pushState(null, "", newUrl);
    }
  },
  removeItem: (key): void => {
  },
};

export const useStore = create<{
  tab: string;
  setTab: (tab: string) => void;
}>()(
  persist(
    (set, get) => ({
      tab: "",
      setTab: (tab: string) => set({ tab }),
    }),
    {
      name: "tab-storage",
      storage: createJSONStorage(() => pathStorage),
    },
  ),
);

Although the browser back/forward buttons change the URL, the store does not update automatically.

Is there anyone who can assist with this issue? I am using Next.js 14, React 18, and Zustand 4.5.

Answer №1

Your Zustand store now has the ability to persist its state to the URL using a custom StateStorage implementation called pathStorage, thanks to the persist middleware. This setup ensures that the URL is updated whenever the state changes and can retrieve the state from the URL when the page is reloaded.
However, there's an issue when using the browser's back/forward buttons as the URL changes, but the store doesn't update automatically. To solve this, you need to listen for URL changes and update the store accordingly. You can achieve this by adding an event listener for the popstate event, which triggers when the user navigates through their history using back or forward buttons. Here's an example of how to do this:

... your imports...

const pathStorage: StateStorage = {
  getItem: (key): string => {
    const pathname = window.location.pathname
    const p = pathname.split("/").filter(Boolean)[3]
    return p
  },
  setItem: (key, newValue): void => {
    const slug = JSON.parse(newValue).state.tab
    const oldSlug = window.location.pathname.split("/").filter(Boolean)[3]
    const newUrl = `/fsd/sdet/class-structure/${slug}`
    if (oldSlug) {
      window.history.replaceState(null, "", newUrl)
    } else {
      window.history.pushState(null, "", newUrl)
    }
  },
  removeItem: (key): void => {
  },
}

export const useStore = create<{
  tab: string
  setTab: (tab: string) => void
}>()(
  persist(
    (set, get) => ({
      tab: "",
      setTab: (tab: string) => {
        set({ tab })
        pathStorage.setItem("tab", JSON.stringify({ state: { tab } }))
      },
    }),
    {
      name: "tab-storage",
      storage: createJSONStorage(() => pathStorage),
    },
  ),
)

// Listen for popstate events to update the store when the URL changes
window.addEventListener("popstate", () => {
  const tab = pathStorage.getItem("tab")
  if (tab) {
    useStore.getState().setTab(tab)
  }
})

Adding this event listener ensures that the store updates correctly when the user navigates with the browser's back/forward buttons, as the popstate event will be triggered each time the URL changes.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Can you explain the contrast between afterInteractive and lazyOnload techniques?

When utilizing the new next/script component, we can make use of two distinct strategies: afterInteractive (default): This strategy is intended for scripts that need to fetch and execute content after the page has become interactive. Examples include tag ...

Why won't both routes for Sequelize model querying work simultaneously?

Currently, I am experimenting with different routes in Express while utilizing Sequelize to create my models. I have established two models that function independently of one another. However, I am aiming to have them both operational simultaneously. A sea ...

Utilize Axios in Vue.js to fetch and process data from a REST API endpoint in C#

After successfully creating and deploying an API on Azure, I am trying to display the response in an alert box using javascript (Vue.js). The test method of my API returns the string "working". You can test the API here. This is the code snippet from my A ...

Creating an overlay with CSS hover on a separate element and the ::before pseudo class

Issue: I am struggling to display the overlay when hovering over the circle element, rather than just the image itself. I have attempted to achieve this using CSS, but can't seem to make it work as intended. Any guidance and examples using JavaScript ...

Halfway opening the dialog drawer

My goal is to create a dialog box that opens up in the lower half of the blue area when a specific button is clicked, while the upper half displays a backdrop. However, I have been facing issues with making the dialog box appear only in the designated area ...

Showing JSX/HTML content depending on the props received

Can you identify the name of this type of expression and do you know in what scenarios it should be applied? {props.type === "big" && <h2>{props.title}</h2>} ...

What is the best method for playing raw audio wav files directly in a web browser?

I'm currently attempting to play wav raw data in the browser that is being transmitted from a Node.js server via socket.io. The main goal is to play the receiving data as quickly as possible without waiting for all the data to be received. I initially ...

Utilize React-markdown to interpret subscript text in markdown format

I tried to create subscript text in an .md file using the following syntax: x_i x~i~ Unfortunately, react-markdown did not interpret this as subscript. After some research, I discovered the package remark-sub-super and implemented it with the plugin ...

Issues have been encountered with Angular 5 when trying to make required form fields work properly

Just created my first Angular app using Angular 5! Currently following the documentation at: https://angular.io/guide/form-validation. Below is the form I have set up: <form class="form-container"> <mat-form-field> <input matInput pl ...

"Enhance Your Website with qTip2 Feature to Load Multiple AJAX Sites Simult

I'm currently utilizing the qTip2 library for my project and I've implemented their AJAX retrieval functions following this example: http://jsfiddle.net/L6yq3/1861/. To enhance my query, I have modified their HTML to include multiple links. The ...

Move the cursor within the text area upon clicking the button

When the "+header" button is clicked, I am looking to automatically position the insertion point inside the text area. Currently, after pressing the button, the text box displays information like address, date, time etc. but the cursor does not start insid ...

Unable to establish an HTTP-Only cookie between a Java backend and a NextJS application

Having trouble with cookies on my NextJS app. After sending an Http-Only cookie in the response to a /login request, I expected it to be saved in the Cookie storage and accessible via req.headers.cookie in ServerSideProps. However, the cookies are not bein ...

unable to decipher a JSON file obtained from Instagram

I have been working on a project that involves parsing a JSON file obtained from the Instagram API. However, I am facing an issue where I can parse the data but I cannot read it properly in my code: <!DOCTYPE html> <html> <head> <meta ...

Altering information with the use of history.pushState throughout time

I've created a template that I want to load using the jQuery .load() function. However, during testing, I discovered that it's not loading at all. Here is the code I'm trying to use for loading: function open() { history.p ...

The deployment of a Reactjs application on Heroku was unsuccessful and crashed unexpectedly

I am currently attempting to deploy my React app on Heroku. The log file shows that the build was successful, but when I try to run it in the browser, I receive an error message after seeing "Starting the development server...". Process exited with status ...

Validating checkboxes using HTML5

When it comes to HTML5 form validation, there are some limitations. For instance, if you have multiple groups of checkboxes and at least one checkbox in each group needs to be checked, the built-in validation may fall short. This is where I encountered an ...

What are some methods for testing enzyme, react-virtualized, and material-ui components?

I am facing a unique testing scenario where I need to interact with a component wrapped in TreeView from material-ui that is nested inside the List component from react-virtualized. Here is my approach: wrapper .find(TreeView) .dive() .find(AutoSiz ...

Creating Layouts with Bootstrap 3

I'm exploring the codepen below in an attempt to mimic the appearance of the image provided consistently across all screen sizes. However, there are two issues that I am encountering - firstly, the green numbers on the first line which represent the d ...

Encountered a problem while trying to add npm package to a Next.JS project

Looking for some assistance with installing an npm package called react-mic. My aim is to enable user audio recording and display the wavelength pattern. I've encountered an error, and you can see my logs and package.json in the provided image. Any he ...

I'm struggling to figure out how to properly utilize NextJs useRouter function

I'm encountering an issue with NextJs useRouter. Within a library, I have defined a function to switch between paths using useRouter. Here is the relevant code snippet: router = useRouter() const actions = [ { ... perform: (e) => router.p ...