Search code examples
javareactjsspring-bootspring-webfluxserver-sent-events

Server sent events being multiplicated when incoming to React


I have a simple Java Spring application that uses server sent events to send data to a frontend React app. Here is the how the backend is written:

 @GetMapping(value = "/plot", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux get() {
        return Flux.interval(Duration.ofMillis(1000))
                .map(interval ->
                        currentStateMapper.map(eventRepository.getAll()).stream()
                                .map(plotDataMapper::map)
                                .collect(Collectors.toList())
                );
    }

What this does is probably less important but the idea is that I store recent events and the repository feeds me all of the events stored (they are stored in an infinispan cache), and then i use some mappers to get the current state and then to map it to objects suitable for visualizing with a XY plot.

It should send a message with the plotting data to the frontend every second. And when I open the endpoint with my browser (Chrome) it works like a charm. can see a new JSON appearing every second. But when I use React's event source to receive the messages instead of 1 message each second I get 6 identical messages each second. Here is the frontend implementation:

const Component = () => {
  const plotDataEventSource = new EventSource(
    "http://localhost:8080/tags/plot/antenna"
  );

  useEffect(() => {
    plotDataEventSource.onmessage = e => {
      console.error(e)

      setData(prevState => {
       /* some data handling */ 
        return newState;
      });
    };
  });

  return ( /* html with plot */ );
}

And so the console.error() gets logged 6 times each second, identical objects. What can be the reason of this behavior?


Solution

  • useEffect will trigger the callback on each render - if you want it to behave like componentDidMount you should pass an empty array as the second argument.

    The second argument is an array of values (usually props):

    1. If any of the value in the array changes, the callback will be fired after every render.
    2. When it's not present, the callback will always be fired after every render.
    3. When it's an empty list, the callback will only be fired once, similar to componentDidMount.