Sessions is temporarily moving to YouTube, check out all our new videos here.

Popper.js: Positioning Libraries Integration in React

Federico Zivolo speaking at React Vienna in June, 2017
Great talks, fired to your inbox 👌
No junk, no spam, just great talks. Unsubscribe any time.

About this talk

Position stuff (poppers, aka tooltips, popovers and drop-downs) next to other stuff with Popper.js, using zero dependencies.


- Okay, so, hey guys. Today I'm going to talk about positioning libraries integration in React. I'm going to talk specifically about Popper.js, that's a library I created and its purpose is to position stuff near other stuff. So, I am Federico Zivolo. You can find me on GitHub and Twitter as FezVrasta and I'm UI Specialist at Quid. Positioning libraries, What's a positioning library? It's a library for making an element stay near to another element. Not quite clear. So, let's try again. Reference, like a button. Popper, it's like tooltip, dropdown, popover. Much clearer. So, the problems. There are several problems with the current solutions like communication between parties, DOM manipulated by third party libraries, node context is meaningful. Let's start with communication between parties. You have to know when the library that is positioning your tooltip has finished doing its stuff, because you want to maybe show the tooltip, you want to rearrange or something to make sure everything is positioned correctly, and with current solutions it's a mess, because they are just like jQuery plugins, and they do their own stuff without you knowing anything about what's happening. Another problem is that you are not able to interfere with their flow, so if you want to interrupt their process, or, like, make it work with React lifecycle it's quite hard. And, the other problem is that you have no way to get the data that has been computed by the library and use it inside your application. The other problem is that manipulate, the DOM manipulated by other party libraries, in this case, the positioning libraries, they are, as I said, like jQuery plugins, they just edit the DOM and if your component is going to re-render you lose any modification that has been applied to the previous library and you get, like, quite a few problems and it doesn't flow it doesn't follow the React lifecycle like I said. So, you don't know when React DOM is rendering, the positioning library is going to do something. It's just something, like, that when you want. So, in this case, we have an example. The button is our reference element, The div is the tooltip and it has some inline style that has been applied by the positioning library. This style is maybe going to maybe get lost in a fit of renders. The next problem is that the node context is meaningful. Node context is basically the position of a node in the DOM tree. Most of the libraries just take your popover node, they move it as direct child of body. This is because it's much easier to compute the position of the tooltip if they know that there's not any relative parent, scrolling container or anything on top of it. This is nice for them because yay, less code to write. It's bad for you because you lose the context, for instance, you may have keyword navigation that is going to break because, you know, the node is not in when its supposed to be. Or, maybe, you have just simulated a practise that makes the screen hard to read but the DOM and the, it will not find the the component anymore and the task is not there. Then, there is the problem that when you move a DOM node to somewhere else, React doesn't know that you have moved it and it's going to freak out. And, the other problem is that, sometimes, you may want to keep the tooltip in the same context of the, where you position it, positioned it originally, because it may have a scrollable container that you want to actually wrap your application, your component and that's a problem because if it's going to be moved off side, it's going to ignore what's happening in that place. So, in this case, we have a button that is child of a section and it's child of a main target and we have a popover that was child of main but has been moved inside the body because of it. Solutions. Yeah, that was hard. Middlewares and lifecycle hooks. Delegate DOM manipulation to react and no node context manipulation. So, middlewares and lifecycle hooks are a thing that is particular to Popper.js and they are onCreate, onUpdate and modifiers. These are three fundamental building blocks of Popper.js and they make it possible to integrate it with any kind of multi-party library. For instance, onCreate and onUpdate are going to be called the first when the tooltip has been positioned at the first time. onUpdate on each subsequent update of the position of the tooltip. Modifiers instead is like a big middleware stack of functions that take the data that has been computed by the previous modifier, modify the data and return the data and they can also do stuff like modify your DOM if you want to. Or, pass the data to React for instance and then React can use the data to do something else. The delegate DOM manipulation to React is taking advantage of modifiers, we are going to override the apply style modifier that is the built in modifier that takes care to manipulate the DOM. In practise, Popper.js doesn't manipulate the DOM in any other place of the code accept for the apply style modifier. If we override this modifier with this function, we are going to take the data, we pass the offset of the popover that has been computed by Popper.js to the component state and then React is going to use this new state to apply the offset to your DOM node using React. You can see that we are returning data at the end because we, as I said, can change modifiers to get one of the others, so if we are going to modify the data, we are going to pass it to the next modifier so that the last modifier takes the updated data and it can work on it. No node context manipulation, this is easy. Basically, a high spanned bunch of followers to make Popper.js work on every possible kind of case that you can think of. We have the button that's child of section. We have our popover that is child of main. It doesn't care that they are in two different node trees or that one may be inside a scrollable container or anything. It will just work because there is a lot of stuff going on. Then, bonus point composition. In practise, you can take the library created by Travis Arnold that's React popper which is our React wrapper around Popper.js that's, it only practically overrides the style, as I described it before, and makes it work with React, providing some useful utilities. You can use it and compose it with any other component that you can think of like: React portal, React re-sizer, React click outside, the React editing. And, in doing so, you can achieve, like, a lot of complex components that do a lot of stuff without having complex logic tied together. This is an example of a tooltip component that have manager as a wrapper of the whole component. Manager is a component provided by React Popper and it basically knows which popper element and which different element should work together. So you can put inside it a Popper and a reference and it would make them dialogue together. Then, we have the click outside component that is going to close the function every time you click outside the reference element or the tooltip element and then we have the target that's basically where the popper is going to be positioned to. We have portal which is another component that is going to move our popper in a different node context but, this time, having React know what's happening and where it's going to be moved. Then, we have popper which is another component provided by React popper which takes a function that has a scheduled update and other properties as a argument and wheels these properties to make re-size aware which is a component that makes it possible to listen to size changes of any component without you having to listen to, like, DOM manipulation or having a loop that checks the size every time. It creates an active re-size element so every time the content is going to change even because maybe its just the task is going to change it so nothing that task script knows about is going to call the scheduled update of Popper.js that's going to then re-position the popper in the right position without you having the tooltip that's going to be in a different position because it always shrinks. And, that's pretty much all so, demo time. Let's hope this worksso. This is a reference element, popper, which is inside a flag. When we will click inside the area that's the grey area, as you can see, yeah, okay. It's going to add, it's going to do anything? Okay. Right, clear? okay. Is this broken? Okay, nice, okay. It's going to add the emojis. One more each to match the emojis inside the element so that there's not space here. It will overflow it. It's going to fit bit on the other side. And every time we click outside, it's going to add more stuff. This is written using basically the same stuff that I used in the example before. And, that's all. Questions?