skip to Main Content

I am encountering an issue in a custom React hook that uses Redux. The problem is related to the inconsistency of a variable’s value inside and outside a function within the same hook. Here’s the simplified code:

import { useAppSelector } from "Redux/helpers";
import { selectAgentState } from "Redux/slices/agentSlice";

const useRemoveRoomNotif = () => {
  const notifications = useAppSelector((state) => selectAgentState(state).notifications);
  console.log("Outside function:", notifications);

  const removeNotif = (rid: string, markNotificationsAsRead) => {
    console.log("Inside function:", notifications);
    const ntf = notifications.filter((notif) => notif.roomID === rid && !notif.isRead);
    if (ntf.length !== 0) markNotificationsAsRead(ntf);
  };

  return { removeNotif };
};

export default useRemoveRoomNotif;

he issue is that the notifications value inside the removeNotif function is different from the value outside the function. I assumed that since it’s the same variable, the values should be the same.

I attempted a workaround using useState, useEffect, and useRef to ensure the correct value is used inside the function:

const useRemoveRoomNotif = () => {
  const notifications = useAppSelector((state) => selectAgentState(state).notifications);
  const [update, setUpdate] = useState(false);
  const [id, seId] = useState("");
  const markAsReadRef = useRef<(notifications: INotification[]) => void>();

  useEffect(() => {
    if (!update) return;
    const ntf = notifications.filter((notif) => notif.roomID === id && !notif.isRead);
    if (ntf.length !== 0 && markAsReadRef.current) {
      markAsReadRef.current(ntf);
    }
    setUpdate(false);
  }, [notifications, markAsReadRef, id, update, setUpdate]);

  const removeNotif = useCallback(
    (rid: string, markNotificationsAsRead: (notifications: INotification[]) => void) => {
      seId(rid);
      markAsReadRef.current = markNotificationsAsRead;
      setUpdate(true);
    },
    [notifications, seId, markAsReadRef]
  );

  return { removeNotif };
};

export default useRemoveRoomNotif;

Can someone please help me understand why the initial implementation is not working as expected and if the workaround is necessary? Any insights or suggestions for improvement would be greatly appreciated. Thank you!

2

Answers


  1. const useRemoveRoomNotif = () => {
      const notifications = useAppSelector((state) => selectAgentState(state).notifications);
      console.log("Outside function:", notifications);
    
      const removeNotif = (rid: string, markNotificationsAsRead, notifications) => {
        console.log("Inside function:", notifications);
        const ntf = notifications.filter((notif) => notif.roomID === rid && !notif.isRead);
        if (ntf.length !== 0) markNotificationsAsRead(ntf);
      };
    
      return { removeNotif: (rid, markNotificationsAsRead) => removeNotif(rid, markNotificationsAsRead, notifications) };
    };
    

    This manner, you may send the current notifications value straight to removeNotif, guaranteeing that it always utilizes the most recent state.

    Login or Signup to reply.
  2. Simply stated, you have an issue of a stale closure over the selected notifications state value in the removeNotif callback function. In other words, removeNotif never sees any updated notifications value in its lifetime.

    Your attempted workaround is also quite convoluted. I believe you could achieve a working solution using just the React useCallback hook to re-enclose the current notifications state value in the removeNotif callback handler.

    import { useCallback } from 'react';
    import { useAppSelector } from "Redux/helpers";
    import { selectAgentState } from "Redux/slices/agentSlice";
    
    const useRemoveRoomNotif = () => {
      const { notifications } = useAppSelector(selectAgentState);
      console.log("Outside function:", notifications);
    
      const removeNotif = useCallback((rid: string, markNotificationsAsRead) => {
        console.log("Inside function:", notifications);
        const ntf = notifications.filter((notif) => notif.roomID === rid && !notif.isRead);
        if (!!ntf.length) markNotificationsAsRead(ntf);
      }, [notifications]);
    
      return { removeNotif };
    };
    

    An alternative would be to "cache" the current notifications value in a React ref and reference the ref’s current value in the removeNotif callback.

    Example:

    import { useEffect, useRef } from 'react';
    import { useAppSelector } from "Redux/helpers";
    import { selectAgentState } from "Redux/slices/agentSlice";
    
    const useRemoveRoomNotif = () => {
      const { notifications } = useAppSelector(selectAgentState);
      const notificationsRef = useRef(notifications);
      console.log("Outside function:", notifications);
    
      // Cache current notifications value in ref
      useEffect(() => {
        notificationsRef.current = notifications;
      }, [notifications]);
    
      const removeNotif = useCallback((rid: string, markNotificationsAsRead) => {
        // Get current notifications ref value
        const notifications = notificationsRef.current;
    
        console.log("Inside function:", notifications);
        const ntf = notifications.filter((notif) => notif.roomID === rid && !notif.isRead);
        if (!!ntf.length) markNotificationsAsRead(ntf);
      }, [notifications]);
    
      return { removeNotif };
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search