Hey everyone,
I am running into a page re-render issue every time I am adding or removing a new item to the global state, for which I am using Zustand.js. This shouldn’t happen, obviously. Everything else is working fine, the cookies are set properly and every time I am coming back to the page, it puts the cookie data into the global state.
I am a bit lost here now, as AI tools are just letting me run in circles and old school googling hasn’t helped me as well. Would be great if someone could have a look into this, and let me know what I need to improve, so the re-render does not happen. I am very helpful for all suggestions and also improvements. Just let me know if you need any further clarification on some things, I tried to include everything as good as I can.
Tech used:
- Next.js 15.2.4
- React 19.0
- Zustand 5.0.1
Page structure
Page component loading cookieDate through server action:
export default async function page() {
const cookieData = await readCookie();
return (
<Tourplaner cookieData={cookieData} />
);
}
Tourplaner component passing the props to the Map component and wrapping it in the provider, where the global state should be used:
"use client";
export default function Tourplaner({ cookieData }: TourplanerProps) {
return (
<CoordinateStoreProvider>
<Map cookieData={cookieData} />
</CoordinateStoreProvider>
</div>
);
}
Map component which holds each tour component that gets displayed depending on which region is chosen. Here, the cookieData gets passed into the global state to set the previous session data:
"use client";
export default function Map({ cookieData }: TourplanerMapProps) {
// setting up the cookieData
const setCoordinates = useCoordinateStore((state) => state.setCoordinates);
useEffect(() => {
if (cookieData) {
setCoordinates(cookieData); // one-time update
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
{showRegion > 0 && (
<div className="tours-container">
{tourData
.filter((tour) => tour.region[0] === showRegion)
.map((tour) => (
<Tour key={uuidv4()} tourInfo={tour} />
))}
</div>
)}
</>
);
}
The tour components hold the CoordinateManager which is handling the adding and removal of the global state:
"use client";
function Tour({ tourInfo }: TourProps) {
return (
<div className="tour-container">
<div className="tour-info">
<CoordinateManager
destination={{
id: tourInfo.id,
title: tourInfo.title.rendered,
}}
/>
</div>
</div>
);
}
export default memo(Tour, (prevProps, nextProps) => {
return prevProps.tourInfo.id === nextProps.tourInfo.id;
});
Here the state will receive input to add or remove the data provided depending on it if the tour coordinates already exist in state.
"use client";
export default function CoordinateManager({ destination }: CoordinateProps) {
// Sync local state with store state
const coordinates = useCoordinateStore((state: { coordinates: Coordinate[] }) => state.coordinates);
const addCoordinate = useCoordinateStore(
(state: { addCoordinate: (coord: Coordinate) => void }) => state.addCoordinate
);
const removeCoordinate = useCoordinateStore(
(state: { removeCoordinate: (id: number) => void }) => state.removeCoordinate
);
const coordExists = coordinates.some((coord) => coord.id === destination.id);
const handleAdd = () => {
if (!coordExists) {
const newCoordinate = {
id: destination.id,
title: destination.title,
};
addCoordinate(newCoordinate);
console.log("CoordinateManager rendered");
} else {
console.log("Coordinate already exists in local state:", destination.id); // Log duplicate attempt
}
};
const handleRemove = () => {
if (coordExists) {
removeCoordinate(destination.id);
} else {
console.log("Coordinate does not exist in local state:", destination.id); // Log missing attempt
}
};
return (
<div className="tour-interaction">
<button onClick={!coordExists ? handleAdd : handleRemove} className="add-tour btn">
{!coordExists ? "Add tour" : "Remove tour"}
</button>
</div>
);
}
This is the store component, calling the server action to update the cookie every time a change takes place:
import { createStore } from "zustand/vanilla";
export type CoordinateStore = {
coordinates: Coordinate[];
addCoordinate: (newCoordinate: Coordinate) => void;
removeCoordinate: (id: number) => void;
setCoordinates: (newCoordinates: Coordinate[]) => void;
};
const defaultState: CoordinateStore = {
coordinates: [],
addCoordinate: () => {},
removeCoordinate: () => {},
setCoordinates: () => {},
};
export const createCoordinateStore = (initState: CoordinateStore = defaultState) => {
//server action to set cookie
async function updateCookie(coordinates: Coordinate[]) {
await setCookie(coordinates);
}
const store = createStore<CoordinateStore>((set) => ({
...initState,
addCoordinate: (newCoordinate) =>
set((state) => {
const exists = state.coordinates.some((coord) => coord.id === newCoordinate.id);
if (exists) return state;
const newCoordinates = [...state.coordinates, newCoordinate];
updateCookie(newCoordinates);
return { coordinates: newCoordinates };
}),
removeCoordinate: (id) =>
set((state) => {
const newCoordinates = state.coordinates.filter((coord) => coord.id !== id);
updateCookie(newCoordinates);
return { coordinates: newCoordinates };
}),
setCoordinates: (
newCoordinates
) =>
set(() => {
updateCookie(newCoordinates);
return { coordinates: newCoordinates };
}),
}));
return store;
};
Last but not least the store provider:
"use client";
import { useStore } from "zustand";
type CoordinateStoreApi = ReturnType<typeof createCoordinateStore>;
const CoordinateStoreContext = createContext<CoordinateStoreApi | undefined>(undefined);
interface CoordinateStoreProviderProps {
children: ReactNode;
}
export const CoordinateStoreProvider = ({ children }: CoordinateStoreProviderProps) => {
const storeRef = useRef<CoordinateStoreApi>();
if (!storeRef.current) {
storeRef.current = createCoordinateStore();
}
return (
<CoordinateStoreContext.Provider value={storeRef.current}>{children}</CoordinateStoreContext.Provider>
);
};
export const useCoordinateStore = <T,>(selector: (store: CoordinateStore) => T): T => {
const store = useContext(CoordinateStoreContext);
if (!store) {
throw new Error("useCoordinateStore must be used within a CoordinateStoreProvider");
}
return useStore(store, selector);
};