I have a nextJs application that uses a global zustand state to alter the mouse component that is present in all my application, the problem is that as all the components are subscribed to setDotType every time one of them changes the state all the others are re-rendered making requests to the backend and worsening the performance, how can I fix it?
zustand global state:
import { create } from "zustand";
// Tipo para el estado de BlackDot
interface BlackDotState {
text: string | null;
setText: (text: string | null) => void;
videoSrc: string | null;
setVideoSrc: (videoSrc: string | null) => void;
dotType: string;
setDotType: (dotType: string) => void;
}
// Creación del store con zustand
const useBlackDotStore = create<BlackDotState>((set) => ({
text: null,
setText: (text) => set({ text }),
videoSrc: null,
setVideoSrc: (videoSrc) => set({ videoSrc }),
dotType: "",
setDotType: (dotType) => set({ dotType }),
}));
export default useBlackDotStore;
My mouse component:
import React, { useRef, useState, useEffect, useContext } from "react";
import gsap from "gsap";
import useBlackDotStore from "./useBlackDotStore";
import Styles from "./Blackdot.module.css";
import VideoMouse from "./VideoMouse";
import { LinkMouse } from "./LinkMouse";
import { ArrowsMouse } from "./ArrowsMouse";
import { Text } from "./Text";
function BlackDot() {
const videoSrc = useBlackDotStore((state) => state.videoSrc);
const dotType = useBlackDotStore((state) => state.dotType);
const text = useBlackDotStore((state) => state.text);
const cursorRef = useRef(null);
const [mousePos] = useState({ x: 0, y: 0 });
const [scale, setScale] = useState(1); // Almacena la escala actual
useEffect(() => {
let x = 0;
let y = 0;
let animationFrameId = 0;
const mousePos = { x: 0, y: 0 };
function updateMousePos(event: any) {
mousePos.x = event.clientX;
mousePos.y = event.clientY;
}
function loop() {
x += (mousePos.x - x) * 0.15;
y += (mousePos.y - y) * 0.15;
gsap.set(cursorRef.current, {
x: x,
y: y,
});
animationFrameId = requestAnimationFrame(loop);
}
window.addEventListener("mousemove", updateMousePos);
loop();
return () => {
window.removeEventListener("mousemove", updateMousePos);
cancelAnimationFrame(animationFrameId);
};
}, []);
useEffect(() => {
const handleMouseDown = () => {
const newScale = scale * 0.85;
gsap.to(cursorRef.current, {
scale: newScale,
ease: "expo.out",
opacity: 1,
});
setScale(newScale);
};
const handleMouseUp = () => {
let newScale = scale / 0.85;
let opacity = 0.7;
if (dotType !== "default") {
opacity = 1;
}
gsap.to(cursorRef.current, {
scale: newScale,
ease: "expo.out",
opacity: opacity,
});
setScale(newScale);
};
window.addEventListener("mousedown", handleMouseDown);
window.addEventListener("mouseup", handleMouseUp);
return () => {
window.removeEventListener("mousedown", handleMouseDown);
window.removeEventListener("mouseup", handleMouseUp);
};
}, [scale, dotType]);
useEffect(() => {
let newScale;
let animationOptions;
switch (dotType) {
case "video":
newScale = 8;
animationOptions = {
scale: newScale,
opacity: 1,
color: "transparent",
borderWidth: "0px",
ease: "expo.out",
};
break;
case "link":
newScale = 2;
animationOptions = {
scale: newScale,
opacity: 1,
background: "#FFF",
color: "transparent",
borderWidth: "0px",
ease: "expo.out",
};
break;
case "arrows":
newScale = 1.5;
animationOptions = {
scale: newScale,
opacity: 1,
color: "#fff",
borderWidth: "1px",
ease: "expo.out",
translate: "none",
background: "transparent",
};
break;
case "hover":
newScale = 2;
animationOptions = {
scale: newScale,
opacity: 1,
background: "#ffffff",
borderWidth: "0px",
color: "#000",
ease: "expo.out",
translate: "none",
};
break;
case "softHover":
newScale = 1.5;
animationOptions = {
scale: newScale,
opacity: 1,
background: "#ffffff50",
borderWidth: "1px",
ease: "expo.out",
translate: "none",
};
break;
default:
newScale = 1;
animationOptions = {
scale: newScale,
opacity: 0.7,
background: "transparent",
color: "#fff",
borderWidth: "2px",
ease: "expo.out",
};
}
setScale(newScale);
gsap.to(cursorRef.current, animationOptions);
}, [mousePos, dotType]);
return (
<div ref={cursorRef} className={Styles.dotMain}>
{dotType === "video" && videoSrc && <VideoMouse videoSrc={videoSrc} />}
{dotType === "link" && <LinkMouse />}
{dotType === "arrows" && <ArrowsMouse />}
{dotType === "hover" && text && <Text text={text} />}
</div>
);
}
export default React.memo(BlackDot);
Component that modifies the state:
import React, { useRef } from "react";
import Styles from "./BigTxtImages.module.css";
import useBlackDotStore from "@/components/Blackdot/useBlackDotStore";
import useScrollTriggerAnimation from "@/hooks/useScrollTriggerAnimation";
export const BigTxtImages = () => {
const ref = useRef<HTMLDivElement | null>(null);
useScrollTriggerAnimation(ref, "simple", 2.5, "Expo.easeOut", 0);
const { setDotType, setVideoSrc } = useBlackDotStore();
const videos: Record<string, string> = {
colección: "videoCard.mp4",
España: "videoCard.mp4",
destilada: "videoCard.mp4",
};
const handleMouseEnter = (word: string) => {
setDotType("video");
setVideoSrc(videos[word]);
};
const handleMouseLeave = () => {
setDotType("");
setVideoSrc("");
};
return (
<div className={Styles.bTextCenter} ref={ref}>
Descubre la mayor{" "}
<span onMouseEnter={() => handleMouseEnter("colección")} onMouseLeave={handleMouseLeave}>
colección
</span>{" "}
de whiskys de{" "}
<span onMouseEnter={() => handleMouseEnter("España")} onMouseLeave={handleMouseLeave}>
España
</span>
, un silencioso testigo de historia{" "}
<span onMouseEnter={() => handleMouseEnter("destilada")} onMouseLeave={handleMouseLeave}>
destilada.
</span>
</div>
);
};
I have tried to separate the state with slices but as they all have to subscribe to setDotType it does not work.