Server sent events being multiplicated when incoming to React - java

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?

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):
If any of the value in the array changes, the callback will be fired
after every render.
When it's not present, the callback will always be fired after every
render.
When it's an empty list, the callback will only be fired once,
similar to componentDidMount.

It is missing some important parts of your code, but the re-render may be happening because you didn't inform useEffect under what conditions it should run, and it can be achieved by a second parameter in the useEffect method.
The way you wrote it, useEffect is triggered every time your component renders. If you make a small change, passing a condition to useEffect it will run only when that condition change. For instance:
useEffect(() => {
plotDataEventSource.onmessage = e => {
console.error(e)
setData(prevState => {
/* some data handling */
return newState;
});
};
}, [data]); // <---- Assuming you are changing something called data.
When your component is rendering, useEffect will compare the value of data and will run only if it has changed.

Related

How to get the latest value emitted in spring web flux using sinks?

I am trying to have a system where I add an object (shifts in this context) and the front end receives that and processes that without refreshing the page and making another api call. Currently, I have two functions, getAllShifts and addShift. When I add a shift I expect the getAllShifts to be updated automatically using sinks. However, the response that I get is not something I expect. The code is shown below:
private Sinks.Many<Shift> shiftSink=Sinks.many().replay().latest();
public Mono<Shift> addShift(Shift shift) throws InterruptedException {
Mono<Shift> newshift= shiftRepository.save(shift);
newshift.subscribe(u-> this.shiftSink.tryEmitNext(u));
return newshift;
}
public Flux<Shift> getAllShifts(){
this.shiftRepository.findAll().subscribe(u-> this.shiftSink.tryEmitNext(u));
shiftSink.asFlux().subscribe(u-> System.out.println(u + "UUUUUUUUUUUUU"));
return shiftSink.asFlux();
}
When the getAllShifts endpoint is engaged from the frontend using eventsource and I add a shift, I expect to receive one event containing the data from that newly added shift. Instead multiple events containing that data are emitted as shown in the picture below.
Any help would be appreciated...

getReactions() returns empty list, regardless of the amount of reactions

When I try to read the reactions added to a message sent I'm always getting an empty list.
MessageBuilder mb = new MessageBuilder();
channel.sendMessage(mb.build()).queue((t);
After adding reactions to it, I execute this code:
System.out.println(t.getReactions().size());
for (MessageReaction r : t.getReactions()) {
System.out.println(r.getReactionEmote().getName());
}
(this is yet to be implemented, I'm just trying to get to understand how I can use it)
I am expecting the output to be the amount of reactions I added, yet System.out.println(t.getReactions().size()); will always print 0 regardless of the amount of reactions added to the message sent.
Furthermore, when iterating of the list containing reactions, it always won't print anything to the console, since the list seems to be empty.
Is there something I need to add that I can use .getReactions()?
What I also tried is using an eventWaiter:
eventWaiter.waitForEvent(GuildMessageReactionAddEvent.class, (event) -> {
return "🎵".equals(event.getReactionEmote().getName()) && !event.getUser().isBot()
&& event.getMessageIdLong() == messageId;
}, (event) -> {
System.out.println("Reacting to reaction");
}, (long) 30, TimeUnit.SECONDS, () -> {
System.out.println("Timeout. No event was registered.");
});
This always outputs the timeout warning, regardless of reactions added.
This is not how JDA works.
The reactions do not get magically updated, you can get the reactions by fetching the message or by listening for the reaction add event.
To use the event waiter properly you must make sure that the same instance is also registered on JDA by using JDABuilder#addEventListeners.
So either listen for the event manually or make sure that you only use once instance of the EventWaiter class in your code that is also registered on the JDA builder.

How to correctly pass data in the Finite State machine in AKKA

I am following this example here from the doc
Here is part of the finite state machine I'm working with
startWith(ACCEPTED, new myData());
when(ACCEPTED, matchEvent(someMesage.class, MyData.class,
(someMessage, myData) -> goTo(EVALUATING).replying(EVALUATING)));
onTransition(matchState(ACCEPTED,EVALUATING, () -> {
// Here I want to update the nextState data and pass it to another actor
// But the nextState data is always the unititalized object which is new Mydata() when the FSM initializes
}));
whenUnhandled(matchAnyEvent(
(state, data) -> stay().replying("received unhandled request " + state.toString())));
initialize();
}
How do I correctly pass data between various states in the state machine?
How should the actor.tell call look like for the actor sending a message to this FSM actor
If I send the following message
MyFSM.tell(new someMessage(myData), getSelf());
It correctly matches the event and the actor changes the state to EVALUATING and sends back an EVALUATING message. BUt what I really want is, modify 'myData' based on this state change and on transition, send this modified data to another actor.
But when I send a message of type someMessage I have no way to send the existing instance of myData and it is always uninitialized as part of the initialization of the state machine.
In other words, I am trying to manage the state of myData with the finite state machine.
How can I achieve his making the best use of the framework?
A working example from the above information will be really useful!
You can use using to provide a new state, like so:
when(ACCEPTED, matchEvent(someMesage.class, MyData.class,
(someMessage, oldData) -> {
MyData newState = new MyData(); // or transform the old into a new one
return goTo(EVALUATING).using(newState).replying(EVALUATING);
}));

RxJava: How to prepend startWith() default emit EVERY TIME parent observable emits?

im trying to have a pattern, where my observable which produces some object, is transformed into domain events like Started, Success, Error emited around the observable producing, if that makes sense
public Observable<BookRenderingEvent> extractAndRenderObservable(String epubPath) {
return extractObservable(epubPath)
.flatMapObservable(extractedEpub -> renderObservable(extractedEpub)
.<BookRenderingEvent>map(renderedEpub -> new BookRenderingEvent.Success(renderedEpub))
.onErrorReturn(t -> new BookRenderingEvent.Error())
.startWith(new BookRenderingEvent.Started()));
}
private Observable<RenderedEpub> renderObservable(ExtractedEpub extractedEpub) {
return Observable.combineLatest(readerConfigObservable(), pagerDimensionsObservable(), ..)
.switchMapSingle(foo -> doRenderObservable()) <--- heavy work
.map(bar -> new RenderedEpub(bar))
}
renderObservable contains a heavy action so I want to emit these state events, so UI can react accordingly (with success containing the extractedEpub object as you can see in the map)
What my problem is that, renderObservable contains combineLatest(), so it "stays open" and emit mutiple times in time, whenever its obervables emit.
So the flow of events is Started, Success, Succes ... Success.
I want it to be Started, Success, Started, Success .. etc. i.e prepend Started event whever combineLatest emits, but my rx knowledge is insufficient.
Thanks
You could insert the following into the observable chain at the right place:
.flatMap( event -> Observable.just( new BookRenderingEvent.Started(), event )
This will emit the Started event before every event that it receives.
Of course, you could add in some logic so that you don't issue Started if the event is Started, etc.
Ok Ive managed to figure it out. The key info I was missing is that right side of flatmap gets subscribed when left side emits. Therefore the startWith had to be moved to the right side of flatmap observable, that gets subscribed to when ever combineLatest emits
public Observable<BookRenderingEvent> extractAndRenderObservable(String epubPath) {
return extractObservable(epubPath)
.flatMap(extractedEpub -> Observable.combineLatest(readerConfigObservable(), pagerDimensionsObservable(), ..)
.switchMap(foo -> renderObservable(extractedEpub)
.<BookRenderingEvent>map(renderedEpub -> new BookRenderingEvent.Success(renderedEpub))
.onErrorReturn(t -> new BookRenderingEvent.Error())
.startWith(new BookRenderingEvent.Started()));
}

Use rx java to load reference data but blocking if necessary

I am trying to implement a solution for storing reference data in the database of my app.
The data is initially stored as JSON files, which I will need to sync from a server on each launch. I have a local copy of the files baked into the app. Each launch I have to check shared preferences for a version. And if it not present, I assume it is the first launch. So i need to read in the files, write the files to the database and fire on completed when that is done. The first screen expects this data to be in the database, so I will be not showing the UI for that screen in this scenario, until the process completes.
However in the future the network call to sync these files can happen asynchronously so want to be able to fire on completed on my observable as soon as i see the shared prefs have a version number and then ill kick of the update completely asynchronously
How can i set up a stream to represent this. I think the stream type will probably be void and i will just fire onCompleted/error as the subscriber doesnt care about the data, only what the process is complete
You could do something like this:
updateChecker.hasUpdates()
.flatMap(hasUpdates -> {
if (hasUpdates) {
return dataUpdater.update();
}
return Observable.just(false);
})
Assuming that
class UpdateChecker {
public Observable<Boolean> hasUpdates() {
return Observable.just(true); // Replace by API call
}
}
class DataUpdater {
public Observable<Boolean> update() {
// update the database here
return Observable.just(true);
}
}

Categories