Repository: novuhq/novu
File: packages/react/src/hooks/useNotifications.ts (TypeScript)

Code before:
  const [hasMore, setHasMore] = useState(false);
  const length = data?.length;
  const after = length ? data[length - 1].id : undefined;

  const sync = (event: { data?: ListNotificationsResponse }) => {
    if (!event.data || (filterRef.current && !isSameFilter(filterRef.current, event.data.filter))) {
      return;
    }
    setData(event.data.notifications);
    setHasMore(event.data.hasMore);
  };

  useEffect(() => {
    const cleanup = on('notifications.list.updated', sync);

    return () => {
      cleanup();
    };
  }, []);

  useEffect(() => {
    const newFilter = { tags, data: dataFilter, read, archived, snoozed, seen };
    if (filterRef.current && isSameFilter(filterRef.current, newFilter)) {
      return;
    }
    notifications.clearCache({ filter: filterRef.current });
    filterRef.current = newFilter;

    fetchNotifications({ refetch: true });
  }, [tags, dataFilter, read, archived, snoozed, seen]);

  const fetchNotifications = async (options?: { refetch: boolean }) => {
    if (options?.refetch) {
      setError(undefined);
      setIsLoading(true);
      setIsFetching(false);
    }
    setIsFetching(true);
    const response = await notifications.list({
      tags,
      data: dataFilter,
      read,
      archived,
      snoozed,
      seen,
      limit,
      after: options?.refetch ? undefined : after,
    });
    if (response.error) {
      setError(response.error);
      onError?.(response.error);

Review comment (performance):
The alternative approach is, to update/prepend the cache with the newly received notification directly, the cache then emits `notifications.list.updated` on update etc etc like I did here https://github.com/novuhq/novu/pull/8885/files#diff-e0018423a5f021324793421dd31c64a4d4edebeee6171ceccf21a6ddca646558R162

But its fairly complicated and it works just for new notifications, but when you mark notif as `read` or `archived` etc, backend emits just `notifications.unread_count_changed` without notification object. We would need to listen to internal WS events, which is another layer of complexity, so I decided to just refetch as we do in `useCounts()`. The one saved API call is probably not worth the added code complexity. 

Code after:
  const [hasMore, setHasMore] = useState(false);
  const length = data?.length;
  const after = length ? data[length - 1].id : undefined;

  useWebSocketEvent({
    event: 'notifications.unread_count_changed',
    eventHandler: () => {
      void refetch();
    },
  });

  useWebSocketEvent({
    event: 'notifications.unseen_count_changed',
    eventHandler: () => {
      void refetch();
    },
  });

  useWebSocketEvent({
    event: 'notifications.notification_received',
    eventHandler: ({ result: notification }) => {
      const currentFilter = filterRef.current;
      const matches = currentFilter ? checkNotificationMatchesFilter(notification, currentFilter) : false;
      if (matches) void refetch();
    },
  });

  const fetchNotifications = useCallback(
    async (options?: { refetch: boolean }) => {
      if (options?.refetch) {
        setError(undefined);
        setIsLoading(true);
        setIsFetching(false);
      }
      setIsFetching(true);
      const response = await notifications.list({
        tags,
        data: dataFilter,
        read,
        archived,
        snoozed,
        seen,
        limit,
        after: options?.refetch ? undefined : after,
      });
      if (response.error) {
        setError(response.error);
        onError?.(response.error);
      } else if (response.data) {
        onSuccess?.(response.data.notifications);
        setData(response.data.notifications);