Cold
Cold2w ago

State not getting updated

I have a project I'm working on currently where I am using zustand for state management. I switched to zustand after encountering similar problems with base state management, but clearly this hasn't helped. I am trying to create a live notification/account update system, where the client uses server-sent events to manage this. The server will send an update every 5 seconds. Now, in the code below, I am very clearly updating the state with this data, but it is not doing anything. Can anyone help me figure out why and help me fix it?
Solution:
Here is what I did: ```typescript const { notifications, setNotifications } = useNotificationStore(); const [localNotifications, setLocalNotifications] = useState< Notification[]...
Jump to solution
31 Replies
Cold
ColdOP2w ago
Cold
ColdOP2w ago
data outputs the correct data. notifications and user do not. These remain the same values as when they are initially set in _app.tsx Relevant code below:
const App = ({ Component, pageProps }: AppProps) => {
const { user, setUser } = useUser();
const [loading, setLoading] = useState(true);
const router = useRouter();

// On data fetch, SSE will handle the delivery of any updates based on bans or account limits.
useEffect(() => {
api.getUser()
.then((data) => {
if (data && data.data) setUser(data.data);
setLoading(false);
})
.catch(() => {
setUser(undefined);
setLoading(false);
});
}, []);

// ... et cetera
const App = ({ Component, pageProps }: AppProps) => {
const { user, setUser } = useUser();
const [loading, setLoading] = useState(true);
const router = useRouter();

// On data fetch, SSE will handle the delivery of any updates based on bans or account limits.
useEffect(() => {
api.getUser()
.then((data) => {
if (data && data.data) setUser(data.data);
setLoading(false);
})
.catch(() => {
setUser(undefined);
setLoading(false);
});
}, []);

// ... et cetera
I am at a loss here and any help would be greatly appreciated I have been debugging this for the past day and a bit and am getting absolutely nowhere
Unknown User
Unknown User2w ago
Message Not Public
Sign In & Join Server To View
Cold
ColdOP2w ago
I've already asked numerous AI tools and none of them have been able to help. ChatGPT 4.0, Copilot Chat etc.
Unknown User
Unknown User2w ago
Message Not Public
Sign In & Join Server To View
Cold
ColdOP2w ago
The above user was unable to help. Anyone else able to provide any insight? Really stuck here
Unknown User
Unknown User2w ago
Message Not Public
Sign In & Join Server To View
Cold
ColdOP2w ago
It's just the standard template for zustand stores So a little something like this Actually scratch that, will send you the code in a few mins @Bao @ bhuynh.dev
import { create } from "zustand";
import { Notification, User, Warning } from "@/types/index.d";

interface UserState {
user?: User | undefined;
setUser: (user: User | undefined) => void;
}

interface NotificationState {
notifications?: Notification[];
setNotifications: (notifications: Notification[]) => void;
}

interface WarningState {
warnings?: Warning[];
setWarnings: (warnings: Warning[]) => void;
}

export const useUser = create<UserState>()((set) => ({
user: undefined,
setUser: (user: User | undefined) => set({ user }),
}));

export const useNotificationStore = create<NotificationState>()((set) => ({
notifications: undefined,
setNotifications: (notifications: Notification[]) => set({ notifications: notifications }),
}));

export const useWarningStore = create<WarningState>()((set) => ({
warnings: [],
setWarnings: (warnings: Warning[]) => set({ warnings: warnings }),
}));
import { create } from "zustand";
import { Notification, User, Warning } from "@/types/index.d";

interface UserState {
user?: User | undefined;
setUser: (user: User | undefined) => void;
}

interface NotificationState {
notifications?: Notification[];
setNotifications: (notifications: Notification[]) => void;
}

interface WarningState {
warnings?: Warning[];
setWarnings: (warnings: Warning[]) => void;
}

export const useUser = create<UserState>()((set) => ({
user: undefined,
setUser: (user: User | undefined) => set({ user }),
}));

export const useNotificationStore = create<NotificationState>()((set) => ({
notifications: undefined,
setNotifications: (notifications: Notification[]) => set({ notifications: notifications }),
}));

export const useWarningStore = create<WarningState>()((set) => ({
warnings: [],
setWarnings: (warnings: Warning[]) => set({ warnings: warnings }),
}));
Unknown User
Unknown User2w ago
Message Not Public
Sign In & Join Server To View
Cold
ColdOP2w ago
I have tried both Because I saw a Reddit thread where someone had that, seemed to work for the user stuff Just for some reason this component doesn't wanna play ball
Cold
ColdOP2w ago
@Bao @ bhuynh.dev for some further context (sorry for ping), this is the console output:
No description
Cold
ColdOP2w ago
Where it says undefined, this is the output of console.log(notifications)
Unknown User
Unknown User2w ago
Message Not Public
Sign In & Join Server To View
Cold
ColdOP2w ago
I already have done - here is the updated code
import { create } from "zustand";
import { Notification, User, Warning } from "@/types/index.d";

interface UserState {
user?: User | undefined;
setUser: (user: User | undefined) => void;
}

interface NotificationState {
notifications?: Notification[];
setNotifications: (notifications: Notification[]) => void;
}

interface WarningState {
warnings?: Warning[];
setWarnings: (warnings: Warning[]) => void;
}

export const useUser = create<UserState>((set) => ({
user: undefined,
setUser: (user: User | undefined) => set({ user }),
}));

export const useNotificationStore = create<NotificationState>((set) => ({
notifications: undefined,
setNotifications: (notifications: Notification[]) => set({ notifications: notifications }),
}));

export const useWarningStore = create<WarningState>((set) => ({
warnings: [],
setWarnings: (warnings: Warning[]) => set({ warnings: warnings }),
}));
import { create } from "zustand";
import { Notification, User, Warning } from "@/types/index.d";

interface UserState {
user?: User | undefined;
setUser: (user: User | undefined) => void;
}

interface NotificationState {
notifications?: Notification[];
setNotifications: (notifications: Notification[]) => void;
}

interface WarningState {
warnings?: Warning[];
setWarnings: (warnings: Warning[]) => void;
}

export const useUser = create<UserState>((set) => ({
user: undefined,
setUser: (user: User | undefined) => set({ user }),
}));

export const useNotificationStore = create<NotificationState>((set) => ({
notifications: undefined,
setNotifications: (notifications: Notification[]) => set({ notifications: notifications }),
}));

export const useWarningStore = create<WarningState>((set) => ({
warnings: [],
setWarnings: (warnings: Warning[]) => set({ warnings: warnings }),
}));
This produces the exact same result
Unknown User
Unknown User2w ago
Message Not Public
Sign In & Join Server To View
Cold
ColdOP2w ago
Right, I get that, but I think we're missing the point here. In my _app.tsx, I am happily able to destructure the store and have it work as intended See here: const { user, setUser } = useUser(); I am destructuring it, yet setUser works as expected:
useEffect(() => {
api.getUser()
.then((data) => {
if (data && data.data) setUser(data.data);
setLoading(false);
})
.catch(() => {
setUser(undefined);
setLoading(false);
});
}, []);
useEffect(() => {
api.getUser()
.then((data) => {
if (data && data.data) setUser(data.data);
setLoading(false);
})
.catch(() => {
setUser(undefined);
setLoading(false);
});
}, []);
Cold
ColdOP2w ago
It pulls down the correct data - avatar, username, and all other relevant info returned by the API
No description
Cold
ColdOP2w ago
@Bao @ bhuynh.dev just for the sake of it I have updated my code as requested and am getting the same result.
const notifications = useNotificationStore((state) => state.notifications);
const setNotifications = useNotificationStore((state) => state.setNotifications);
const notifications = useNotificationStore((state) => state.notifications);
const setNotifications = useNotificationStore((state) => state.setNotifications);
Cold
ColdOP2w ago
No description
Unknown User
Unknown User2w ago
Message Not Public
Sign In & Join Server To View
Cold
ColdOP2w ago
It is being called within _app.tsx due to the NotificationProvider component I have written for creating the actual notification.
return (
<main className={poppins.className}>
<AnimatePresence>
{loading ? (
<motion.div
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
}}
exit={{
opacity: 0,
}}
>
<Loading />
</motion.div>
) : (
<NotificationProvider>
<motion.div
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
}}
exit={{
opacity: 0,
}}
>
<SseHandler />
<NavBar />
<Component {...pageProps} />
</motion.div>
</NotificationProvider>
)}
</AnimatePresence>
</main>
);
return (
<main className={poppins.className}>
<AnimatePresence>
{loading ? (
<motion.div
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
}}
exit={{
opacity: 0,
}}
>
<Loading />
</motion.div>
) : (
<NotificationProvider>
<motion.div
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
}}
exit={{
opacity: 0,
}}
>
<SseHandler />
<NavBar />
<Component {...pageProps} />
</motion.div>
</NotificationProvider>
)}
</AnimatePresence>
</main>
);
SseHandler must be a child of the provider for it to use the hook
Unknown User
Unknown User2w ago
Message Not Public
Sign In & Join Server To View
Cold
ColdOP2w ago
Yes, specifically in SseHandler it seems any state changes are completely disregarded As I have also tested this with useUser and useWarningStore
Unknown User
Unknown User2w ago
Message Not Public
Sign In & Join Server To View
Cold
ColdOP2w ago
Wait a minute.. could it be something to do with SseHandler not returning anything to render? Just out of curiosity I am going to try moving the functionality to my NavBar component...
Unknown User
Unknown User2w ago
Message Not Public
Sign In & Join Server To View
Cold
ColdOP2w ago
Nope, didn't fix it
Unknown User
Unknown User2w ago
Message Not Public
Sign In & Join Server To View
Cold
ColdOP2w ago
Not quite sure what you mean by this? Are you saying, set it to [] and then set it to [...data]? @Bao @ bhuynh.dev @FinalDev Hi all, I have completely refactored my code. Instead of the previous solution, I have moved the SSE functionality back into _app.tsx, with a separate Watcher component that watches for changes to the notifications store, pushing any notifications marked with shouldNotify. Now interestingly, this works to an extent. My notifications get pushed successfully, and what should happen, is on the next pass, all existing notifications should have the shouldNotify prop marked as false. But this interestingly doesn't happen. Instead, if I run a console.log for notifications in _app.tsx, this always returns undefined!!! What's stranger is that if I, say for example, make a change to my code and save it whilst running in dev mode, the notifications state updates correctly with console.log outputting the correct data! What the hell is going on!?!?!?! New code in _app.tsx
useEffect(() => {
if (!loading && user) {
const eventSource = new EventSource(
process.env.NEXT_PUBLIC_BACKEND_URI + "/users/@me/events",
{
withCredentials: true,
},
);

eventSource.onerror = (error) => {
console.error("EventSource failed: ", error);
eventSource.close();
}

eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);

if (user) {
let notifData: Notification[] = data.notifications;

const existingUUIDs = new Set(notifications?.map(notification => notification.uuid));
console.log(notifData + "hi");
console.log(notifications);

notifData.forEach(notification => {
notification.shouldNotify = !existingUUIDs.has(notification.uuid);
});

setNotifications(notifData);
setWarnings(data.warnings);

setUser({
...user,
banned: data.banned,
limited: data.limited,
});
} else eventSource.close();
}

return () => eventSource.close();
}
}, [loading]);
useEffect(() => {
if (!loading && user) {
const eventSource = new EventSource(
process.env.NEXT_PUBLIC_BACKEND_URI + "/users/@me/events",
{
withCredentials: true,
},
);

eventSource.onerror = (error) => {
console.error("EventSource failed: ", error);
eventSource.close();
}

eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);

if (user) {
let notifData: Notification[] = data.notifications;

const existingUUIDs = new Set(notifications?.map(notification => notification.uuid));
console.log(notifData + "hi");
console.log(notifications);

notifData.forEach(notification => {
notification.shouldNotify = !existingUUIDs.has(notification.uuid);
});

setNotifications(notifData);
setWarnings(data.warnings);

setUser({
...user,
banned: data.banned,
limited: data.limited,
});
} else eventSource.close();
}

return () => eventSource.close();
}
}, [loading]);
watcher.tsx
import { useNotification } from "@/components/notification/index";
import { useNotificationStore } from "@/components/user";
import { useEffect } from "react";

const Watcher = () => {
const notify = useNotification();
const { notifications, setNotifications } = useNotificationStore();

// Watches for changes to notifications
useEffect(() => {
if (notifications) {
for (const notification of notifications) {
if (notification.shouldNotify) {
notify({
type: notification.type,
title: notification.title,
content: notification.content,
});
}
}
}
}, [notifications]);

return null;
}

export default Watcher;
import { useNotification } from "@/components/notification/index";
import { useNotificationStore } from "@/components/user";
import { useEffect } from "react";

const Watcher = () => {
const notify = useNotification();
const { notifications, setNotifications } = useNotificationStore();

// Watches for changes to notifications
useEffect(() => {
if (notifications) {
for (const notification of notifications) {
if (notification.shouldNotify) {
notify({
type: notification.type,
title: notification.title,
content: notification.content,
});
}
}
}
}, [notifications]);

return null;
}

export default Watcher;
Now, my first thought was that maybe the same thing was happening, where notifications does not get updated. But if this were the case, then the Watcher component would not have been able to detect the change! My understanding of passing a variable into the array for useEffect is that this watches for changes to the value, running the method passed if it changes I have managed to fix this! But I am absolutely not happy with the solution.
Solution
Cold
Cold2w ago
Here is what I did:
const { notifications, setNotifications } = useNotificationStore();
const [localNotifications, setLocalNotifications] = useState<
Notification[]
>([]);
const { warnings, setWarnings } = useWarningStore();
const [loading, setLoading] = useState(true);
const router = useRouter();

useEffect(() => {
const existingUUIDs = new Set(
notifications?.map((notification) => notification.uuid),
);
localNotifications.forEach(
(notification) =>
(notification.shouldNotify = !existingUUIDs.has(
notification.uuid,
)),
);
setNotifications(localNotifications);
}, [localNotifications]);

useEffect(() => {
if (!loading && user) {
const eventSource = new EventSource(
process.env.NEXT_PUBLIC_BACKEND_URI + "/users/@me/events",
{
withCredentials: true,
},
);

eventSource.onerror = (error) => {
console.error("EventSource failed: ", error);
eventSource.close();
};

eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);

if (user) {
setLocalNotifications(data.notifications);
setWarnings(data.warnings);

setUser({
...user,
banned: data.banned,
limited: data.limited,
});
} else eventSource.close();
};

return () => eventSource.close();
}
}, [loading]);
const { notifications, setNotifications } = useNotificationStore();
const [localNotifications, setLocalNotifications] = useState<
Notification[]
>([]);
const { warnings, setWarnings } = useWarningStore();
const [loading, setLoading] = useState(true);
const router = useRouter();

useEffect(() => {
const existingUUIDs = new Set(
notifications?.map((notification) => notification.uuid),
);
localNotifications.forEach(
(notification) =>
(notification.shouldNotify = !existingUUIDs.has(
notification.uuid,
)),
);
setNotifications(localNotifications);
}, [localNotifications]);

useEffect(() => {
if (!loading && user) {
const eventSource = new EventSource(
process.env.NEXT_PUBLIC_BACKEND_URI + "/users/@me/events",
{
withCredentials: true,
},
);

eventSource.onerror = (error) => {
console.error("EventSource failed: ", error);
eventSource.close();
};

eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);

if (user) {
setLocalNotifications(data.notifications);
setWarnings(data.warnings);

setUser({
...user,
banned: data.banned,
limited: data.limited,
});
} else eventSource.close();
};

return () => eventSource.close();
}
}, [loading]);
Cold
ColdOP2w ago
So, to summarize, I have created a separate localNotifications state that is not shared between components. This acts as an intermediary store, so that the data can be processed properly, setting shouldNotify accordingly Once all notifications have had their relevant props applied, for some reason, setNotifications then works.

Did you find this page helpful?