reactjstypescriptrxjs

Rxjs: Consumer observes multiple times after one next() call in React


I am casting some data from one component on button click to another. After one next call consumer fires multiple times (the number of those is also inconsistent) while I was expecting single reaction.

assistant service:

import { Observable, Subject } from 'rxjs';
import { EMCategory } from '../types';

const subject:Subject<EMCategory[]> = new Subject();

export const assistant = {
    setTasksQuadrants: (tasks: EMCategory[]):void => subject.next(tasks),
    getTasksQuadrants: ():Observable<EMCategory[]> => subject.asObservable()
};

Component which emits values:

import { EMCategory} from "../types";
import { Button } from "./ui_elements/Button";
import { assistant } from "../services/assistant.service";

export function Controls() {
    function evaluateMatrix():void {
        const quadrantsToAssist: EMCategory[] = [];
        quadrantsToAssist.push("q1");
        assistant.setTasksQuadrants(quadrantsToAssist);
    };

    return (
        <footer className="col-span-5 row-span-1">
            <Button text="Apply" fn={evaluateMatrix} />
        </footer>
    );
}

Button element component (altought I don't think it's revelant here):

import { MouseEventHandler } from "react";

export interface ButtonProps {
    text: string,
    fn?: MouseEventHandler<HTMLButtonElement>,
}

export function Button(props: ButtonProps) {
  return (
    <button onClick={!!props.fn ? props.fn : undefined } >{props.text}</button>
  );
}

Finally the component with consumer:

import { assistant } from "../../services/assistant.service";
import { EMCategory, Task } from "../../types";

export function AssistantContainer() {
  assistant.getTasksQuadrants().subscribe(
    (quadrants:EMCategory[]) => console.log(quadrants)
  );

  return (
    <div>
    </div>
  )
}

Solution

  • This issue is a case of mounting and unmounting components where the consumer fires multiple times with inconsistent numbers of calls, most likely because each time your AssistantContainer component mounts, it subscribes to the observable without unsubscribing when the component unmounts. This usually lead to multiple subscriptions being active at the same time, each responding to the next call from the subject.

    I have used useEffect hook to help unsubscribe and provide a way to clean up after each call.

    I made some changes, here is a code snippet to guide you

    import { useEffect } from "react";
    import { assistant } from "../../services/assistant.service";
    import { EMCategory } from "../../types";
    
    export function AssistantContainer() {
      useEffect(() => {
        // Subscribe to the observable
        const subscription = assistant.getTasksQuadrants().subscribe(
          (quadrants: EMCategory[]) => console.log(quadrants)
        );
    
        return () => {
          subscription.unsubscribe();
        };
      }, []); 
    
      return (
        <div>
        </div>
      );
    }