Creating a reusable popover system with React and MobX

In any big application you might want to show a quick, dismissable information or action on an item. This is usually done with popovers. Let me first explain what a popover is.

A popover is a UI element that typically floats around another element, providing action for that element. One example would be the notification bell on many web applications, you click it and a floating element showing your notifications appear. A tooltip can also be considered a popover, as it also floats around an element, though its position may depend on your mouse pointer and not the element.

React is great for creating reusable components to use in your UI, so implementing this should be pretty easy, right?

The issue with popovers is that many of them have different behaviours and features, and you wouldn’t want to write 3 different systems for each popover, but it becomes difficult when there’s so much to keep in mind.

Here’s how I solved it:

  1. Render a component, in this case named PopoverOverlay in the top level of your application
  2. Keep a store that has an array of data that is used to render your popovers
  3. Make a shortcut component to render each popover with the right type
  4. Render the popovers in the overlay

The store

We need to keep the state of our popovers in the store, I’m using MobX but you do not have to. Any state management library can be used.

Our store is very simple:

Here we have the observable array items as a property on the store, a method that adds a popover and a method that dismisses (close) a popover.

Each popover item should have the following properties: key , renderPopoverContent , onOpen and onClose. The options is arbitrary data but the type must be specified.

The Overlay

The overlay is responsible for rendering the popovers in the root of our application. It’s important to render the popovers as a layer on top of the application because then you do not have to consider CSS positioning issues that might arise if you render the popover locally in the document.

Our overlay is also very simple:

We inject the PopoverStore into the component (this assumes you’re using the mobx-react library) and make the component an observer. We then render each popover that is currently in the store, which brings us to…

The PopoverItem

Now that we have our store and overlay we want to render an actual popover, and here come the tricky questions:

  1. What behaviour should the popover have? Should it follow the mouse, should it be fixed to an item, or maybe it should just be fixed to a set position in the window?
  2. What if our popover is overflowing the document, how can we avoid that?
  3. How do we communicate between popovers and the components that spawn them?

An easy way to solve the first question is to look back at the type property that we require for each popover. The PopoverItem component might look like this:

To keep this article short and simple, I will not be covering the ElementPopover but imagine a popover that positions next to a HTML element, an example would be a notification panel.

As you can see all that the PopoverItem component is responsible for is rendering the popover based on what type. It then passes its own props into the rendered popover so they can be used.

Let’s say we want to render a simple tooltip that follows your cursor.

For that we have the CursorPopover as an example:

All that this component does is follow the mouse pointer and call renderPopoverContent in its body.

It also calls the onOpen hook to let the component spawning it know that it has spawned.

The outer div is what we refer to as the “anchor”

Align if required?

Our popover should not overflow the document, so I’ve made an easy function to avoid that:

This ensures that if the popover were to overflow the document, it would flip to the other side of the anchor.

Communicating between the popover and the logic that spawns it

Remember the onOpen and onClose hooks we added when adding a popover to the store? These are very useful when you want to for example avoid the popover from being dismissed when clicked on, as you can check inside your onClick handler in your component if the popover was clicked or not:

if (event.target == popoverRef) return false;

Here’s an example of what your code for spawning a popover might look like:

As you can see we get the ref of the popover in the onOpen hook.

Spawning a popover manually

Now we want to spawn our popover to test. Using the store method we can easily spawn a popover like this:

PopoverStore.addPopover(() => {
return 'Test!';
});

I’ve ommited the other arguments of the method for the sake of simplicity.

If everything is done correctly, this should spawn a popover following your cursor which says “Test!” Great work!

Wrapping up

This article was meant convey a nice way to handle popovers, but there are many ways to do it.

I did not go into much detail of components that would spawn your popovers, but that’s up to you really, and you can also create custom types of popovers, in this article we covered CursorPopover but there could be many more different behaviours you’d like.

Thanks for reading!

This was my first article on Medium, and my first programming article. I would love to know if you liked it or if you think I should just stick to programming and leave writing to the pros.

Like what you read? Give Sebastian R a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.