All files / src/providers reaction.tsx

90.47% Statements 57/63
91.66% Branches 11/12
100% Functions 2/2
90.47% Lines 57/63

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 921x 1x 1x     1x     1x 1x           1x           1x 1x 1x   1x 6x   6x 2x 2x   4x 4x   1x 4x 4x 4x     4x 4x   4x   3x 3x 3x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x   1x             1x 1x 1x 1x 3x 3x 3x 3x 3x     3x 3x 3x 4x   4x   4x 4x 4x 4x   4x  
import { createContext, useContext, useEffect, useState } from 'react';
import { Effect, Stream, Fiber } from 'effect';
import { toast } from 'sonner';
 
// Services
import { subscribeService } from '@/services/subscription';
 
// Stores
import { reactionStore } from '@/stores/reaction';
import { sentimentStore } from '@/stores/sentiment';
 
// Types
import { WSReactionData } from '@/types/meal';
 
// Constants
import { MESSAGES } from '@/constants';
 
interface ReactionContextValue {
  data: WSReactionData;
}
 
const ReactionContext = createContext<ReactionContextValue | undefined>(
  undefined,
);
 
export const useReaction = () => {
  const context = useContext(ReactionContext);
 
  if (!context) {
    throw new Error(MESSAGES.OPTION_DATA_CONTEXT_ERROR);
  }
 
  return context;
};
 
export const ReactionProvider = ({
  children,
  currentOrigin,
}: {
  children: React.ReactNode;
  currentOrigin?: string;
}) => {
  const [data, setData] = useState<WSReactionData>({});
 
  useEffect(() => {
    // Run subscription as an Effect fiber
    const fiber = Effect.runFork(
      Effect.scoped(
        subscribeService(currentOrigin).pipe(
          Effect.flatMap((stream) =>
            stream.pipe(
              Stream.tap((update) =>
                Effect.sync(() => {
                  if (update.reaction) {
                    setData((prev) => ({ ...prev, ...update.reaction }));
                    Object.entries(update.reaction).forEach(([id, stats]) => {
                      reactionStore.setData(id, stats);
                    });
                  }
 
                  if (update.sentiment) {
                    Object.entries(update.sentiment).forEach(
                      ([commentId, sentiment]) => {
                        sentimentStore.setData(commentId, sentiment);
                      },
                    );
                  }
                }),
              ),
              Stream.runDrain, // consume the stream
            ),
          ),
          Effect.catchAll((err) => Effect.sync(() => toast.error(err.message))),
        ),
      ),
    );
 
    // Cleanup: interrupt fiber on unmount
    return () => {
      Fiber.interrupt(fiber);
    };
  }, [currentOrigin]);
 
  const contextValue: ReactionContextValue = { data };
 
  return (
    <ReactionContext.Provider value={contextValue}>
      {children}
    </ReactionContext.Provider>
  );
};