Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update notification to snackbar #919

Merged
merged 5 commits into from
Feb 13, 2022

Conversation

lucasleite01
Copy link
Contributor

Snackbar displaying device name and event.

image

useEffect(() => {
events.forEach((event) => {
setNotification({
message: `${devices[event.deviceId]?.name}: ${t(prefixString('event', `${event.type}`))}`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do this?

Suggested change
message: `${devices[event.deviceId]?.name}: ${t(prefixString('event', `${event.type}`))}`,
message: `${devices[event.deviceId]?.name}: ${t(prefixString('event', event.type))}`,

horizontal: 'right',
}}
open={notification.show}
autoHideDuration={5000}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have both autoHideDuration and setTimeout? Can you please explain how it works.

return null;
useEffect(() => {
events.forEach((event) => {
setNotification({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to support multiple events.

Comment on lines 23 to 24
message: '',
show: false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need two variables? Can't we just use message?

@tananaev
Copy link
Member

Can you also please rename issue to something better.

@lucasleite01 lucasleite01 changed the title Resolves traccar/traccar-web#892 Update notification to snackbar Feb 11, 2022
@@ -17,6 +17,7 @@
"maplibre-gl": "^1.15.0",
"material-ui-dropzone": "^3.5.0",
"moment": "^2.29.1",
"notistack": "^1.0.10",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like adding new library just for supporting multiple notifications. Can't we just stack default ones?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can, but we basically would have to do that notistack does. There's a discussion about stacking snackbars.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say if it's too complicated or requires too much code, let's only support a single one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, this was my first approach. But since we cannot control when we will receive events from websocket we need to have some kind of list or queue for the notifications that arrive.

Otherwise, if we receive an event from websocket and just update the component state it could lead to too much re-rendering if many events arrive in short time, causing error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would there be an error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because an event can arrive and change the component state, generating a re-rendering just in the same time the component is being rendered.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine. Why would cause an error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I might be mistaken:

“Too many re-renderers” is a React error that happens after you have reached an infinite render loop, typically caused by code that in a useEffect hook or the main body of the component itself unconditionally calls state setters.

I'll have to try something like the first approach but keeping in mind the multiple events concern.

@lucasleite01
Copy link
Contributor Author

Anton, your question about multiple events pointed a big problem in the implementation, so I searched and found notistack package.

It uses the same Snackbar from MaterialUI and handle multiple Snackbars by enqueuing the messages and showing them later with a better control. I think this made the implementation more simple.

Now, the notification appears without device name, though, because when the notification arrives we cannot guarantee that we already have loaded the devices:

image

@tananaev
Copy link
Member

Not showing device is not acceptable. It basically makes it pointless.

@lucasleite01
Copy link
Contributor Author

Not showing device is not acceptable. It basically makes it pointless.

Right. We need to think in a better way to ensure that we cover multiple events notification and to have the devices when showing the notification.

@tananaev
Copy link
Member

I also don't like adding dependency. Is there no way to display multiple toast as is?

@@ -83,7 +74,30 @@ const SocketController = () => {
return null;
}, [authenticated]);

return null;
useEffect(() => {
setNotifications(events.map((event) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to save events in the state? Can't we just do this directly and call setNotifications?

Also, can we please not reuse event object. It gets confusing. Create a new object for notifications.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to save events in the state? Can't we just do this directly and call setNotifications?

I tested that way, but here we don't have the devices yet:

socket.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.devices) {
        dispatch(devicesActions.update(data.devices));
      }
      if (data.positions) {
        dispatch(positionsActions.update(data.positions));
      }
      if (data.events) {
        // setEvents(data.events);
        // if we use setNotifications here, we don't have devices yet to construct the message.
        // something like that will return undefined for devices name:
        setNotifications(events.map((event) => {
          event.message = `${devices[event.deviceId]?.name}: ${t(prefixString('event', event.type))}`;
          event.show = true;
          setTimeout(() => event.show = false, 5000);
          return event;
        }));
      }
    };

Also, can we please not reuse event object. It gets confusing. Create a new object for notifications.

Do you mean to rename events?

const [events, setEvents] = useState([]);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean. In that case:

  1. Don't reuse the same event object. Create a new object for notification.
  2. Set devices as a dependency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Oh, I see now. Do you mean that:
    setNotifications(events.map((event) => {
      const notification = {
        message: `${devices[event.deviceId]?.name}: ${t(prefixString('event', event.type))}`,
        show: true,
      };
      setTimeout(() => notification.show = false, 5000);
      return notification;
    }));
  1. Devices as dependency will trigger multiple snackbars with the same notification every time devices were updated.

setTimeout(() => event.show = false, 5000);
return event;
}));
}, [events]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also depends on the devices.

Copy link
Contributor Author

@lucasleite01 lucasleite01 Feb 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses devices, but we don't want this to be triggered every time devices update because devices are updated frequently. If we add devices to the array the snackbars will be displayed every time new devices arrive even if a new event wasn't arrived.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, wait.. you need to clear notifications once they are dismissed. Looks like that's another thing that's missing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. I'll do that.

@lucasleite01
Copy link
Contributor Author

Now I used autoHiddeDurantion with 5 seconds and ater that time the onClose function is called to remove the notification from events.

Copy link
Member

@tananaev tananaev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything else looks good.

id: event.id,
message: `${devices[event.deviceId]?.name}: ${t(prefixString('event', event.type))}`,
show: true,
close: () => setEvents(events.filter((e) => e.id !== event.id)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that there's a callback in a data object. Why do we need it here? We can just call it directly in onClose.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@tananaev tananaev merged commit cab6ad3 into traccar:master Feb 13, 2022
@tananaev
Copy link
Member

Merged, thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants