Skip to content

Change positioning engine library: Migrate from react-tether to react-popper (Proposal) #217

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

Closed
JanHamara opened this issue May 2, 2022 · 11 comments
Labels
conversation needed Issues that may need to go to a broader conversation 📝 RFC Imporant proposals which impact the library

Comments

@JanHamara
Copy link
Contributor

Change positioning engine library: Migrate from react-tether to react-popper (Proposal)

This proposal comes up as a result of the integration process of the new Popover component and realisation of numerous weaknesses and drawbacks of react-tether that I believe don't allow the integration of Popover component according to latest best practices and standards in development of React components, as well as according to our Chapter agreements.

A complete comparison in size, usage and features differences between the two libraries (in depth below):

Library react-tether react-popper
Core Library tether popperjs
Core Lib Latest Release 26 March 2021 4 days ago
Core Lib Size (minified) 21.3kb 20.5kb
--- --- ---
React Wrapper Lib (what we use) @react-tether @react-popper
Latest Release 13 Jan 2020 4 days ago
Weekly Downloads ~ 40 thousand ~ 6.3 million
Size 33 kB (+ 21.3kb @tether) 5.5 kB (+ 20.5kb @popperjs/core)
Issues / Pull Requests 2 / 0 12 / 16
Dependents (projects) 172 2 338
Used by (developers) ~ 1 200 ~ 341 000
React 18 Support No Yes PR #440
Can use React Portal No Yes
Active Contributors Community No Yes Discussions

Maintainability & Currency of the Library

First thing that strikes clear from this comparison is that react-tether is not really being developed or maintained anymore, the latest release of core library dates back to over a year ago in March 2021 and the wrapper library even later, latest release having been over 2 years ago - 13 Jan 2020. Meanwhile, react-popper seems to keep consistent with releases with latest one 4 days ago.

Same goes for the current activity on their respective repositories, react-tether has only 2 issues and 0 pull requests, against 12 issues / 16 pull requests on react-popper repo. In addition to that, react-popper has an active contributors community on its repo's discussions, something that cannot be said about react-tether. It seems safe to say, that react-tether hasn't really been maintained since around the start of 2020.

Usage (Downloads / Users)

The fact that react-tether is being hardly used anymore is also visible through the user and download numbers. react-tether has around 40 000 weekly downloads against 6.3 MILLION weekly downloads of react-popper. 172 dependent projects on react-tether against 2 338 dependent projects on react-popper, as well as ~1200 developers using react-tether vs. ~ 341 000 devs using react-popper are pretty similar ratio of difference as downloads.

Size

There is a pretty significant difference in sizes of these deps too. react-tether with its core dependency included weights at 54.3kb while react-popper with its core dependency weights at 26kb, that is half of react-tether size.

React 18 Support

Probably the most important and relevant difference (also related to: [Issue #212]) is that react-popper devs have already migrated the lib to use and support React 18 features, in its latest release, which would enable us to already support React 18 features in components that use positioning lib (Popover, and consequently also Tooltip and Datepicker components). Looking at activity on react-tether, we cannot really expect it will support React 18 any time soon, if ever.

With React 18 comes also possibility to use latest features and methodologies, for example: concurrent rendering, rendering with React Portal, etc.


Functional Programming & Hooks

Because react-tether TetherComponent is declared as a class component, there is a couple more disadvantages that need to be considered about react-tether here:

Library react-tether react-popper
Uses Functional Components (Chapter Agreements - Functional programming over other paradigms...) No Yes
Uses React Hooks (Chapter Agreements - We use React Hooks) No Yes
API is extendable and allows us to enable custom behaviour (e.g. we need the Popover to hide when we click outside it) No Yes (exposing the update method - destructured from usePopper hook)
We can flip Popover content vertically when there is no space in default position (e.g. during scrolling) Yes Yes
We can specify placement of the Popover Content Yes Yes
We can specify the offset of the Popover Content from the trigger Yes Yes

However, there is quite a big difference in placement and offset setting with react-tether and react-popper...

With react-tether, we have to re-declare in code every single option of placement with helper function so that we can format the attachment value correctly, to be passed into TetherComponent (because we have multiple components defining different sets of placements), but also we have to manually redefine the offset based on the placement of the Popover content

react-popper on the other side, already provides type definitions for all placement options (so we don't have to redefine them in our source code) -> we only need to pass the desired placement and it deals with the positioning. In addition react-popper also automatically updates the selected offset value, so that it fits with current placement. (e.g. you change positioning from top to bottom -> offset changes from bottom margin to top margin)

Also about placement and staying in the viewport...

If you force placement that cannot possibly be positioned in the remaining space in the window (e.g. your trigger is on the left edge of the screen and you apply placement = left), react-tether will not adjust the position of the content, it will flow outside the viewport and will not be visible (which is, of course, bad UX).

react-popper detects that for you out of the box, and always repositions the Popover content somewhere else, where it can be fully visible in the viewport.

Loom presentation of this behaviour: https://www.loom.com/share/5b2bfc065c304284bf3a6988353b18d3


Which components would be affected by this change?

  • Datepicker : Could be very easily refactored to use the new Popover component (share the logic).
  • Tooltip : Could also be easily refactored + using react-popper based Popover would allow us to get rid of quite a lot of helper code on Tooltip component, that specifies the offset per each single placement option + a helper function that calculates attachment from placement (all this stuff is not needed with react-popper).

If interested in more in depth explanation of why the FloatingUI react-popper implementation is better with much modern API, as well as much better browser compatibility than original Popper or Tether libs, in regards to using JS for repositioning, clipping and overflowing issues, and more, check out this article: https://blog.logrocket.com/popper-vs-floating-ui/

@nlopin nlopin added conversation needed Issues that may need to go to a broader conversation 📝 RFC Imporant proposals which impact the library labels May 2, 2022
@martimalek
Copy link
Contributor

Great job writing this RFC! I believe it makes a lot of sense to make this change.
If we go ahead with it we'd need to create issues for the components that are affected (Datepicker and Tooltip)

@atomiks
Copy link

atomiks commented May 3, 2022

FWIW react-popper is also not that new and is not undergoing active development at this current time. Popper (2016) was rebranded to Floating UI (2021) to become a more ambitious project that provided more functionality. Tether was sort of superseded by Popper since around 2017.

I recommend using Floating UI's new React DOM package: https://floating-ui.com/docs/react-dom

Read more about the change: https://floating-ui.com/docs/motivation

@div-Leo
Copy link
Contributor

div-Leo commented May 3, 2022

Thank you very much @JanHamara this is an amazing RFC, and the proposal looks good and makes sense.

Agree with @martimalek. I believe this should not add any breaking change in the two affected components so we should be able to proceed with the migration without any issue.

@div-Leo
Copy link
Contributor

div-Leo commented May 3, 2022

FWIW react-popper is also not that new and is not undergoing active development at this current time. Popper (2016) was rebranded to Floating UI (2021) to become a more ambitious project that provided more functionality. Tether was sort of superseded by Popper since around 2017.

I recommend using Floating UI's new React DOM package: https://floating-ui.com/docs/react-dom

Read more about the change: https://floating-ui.com/docs/motivation

Thanks @atomiks for sharing. It looks very interesting we might open a spike and consider the library for the change.

@JanHamara
Copy link
Contributor Author

Hello @atomiks, thank you very much for your comment and insight!

I would just have a couple of questions about what you've said and recommended :)


Could you please clarify what repository/project you refer to, when you mention ''react-popper not undergoing active development at this current time'' ?

The module that this RFC talks about and proposes using is part of Floating UI and it's floating-ui/react-popper and it seems to be maintained as described in RFC, with latest release 4 days ago bringing support for React 18 (idk if by active development you meant new features, or we were referring to different libraries).

On the other side, the https://floating-ui.com/docs/react-dom seems to be still using ReactDOM.render method, instead of the new createRoot method from react-dom/client.

From this I would assume that it's not migrated to use React 18 features yet.


To be honest, I feel like I am a little bit confused as to which of these two is the ''latest solution'' or the best solution, as the one we've introduced in this RFC seems to be part of Floating UI + ready for React 18, while the one you have suggested seems not yet.

Also this article that mentions your contribution: https://opencollective.com/floating-ui/updates/popper-is-evolving-into-floating-ui talks about new @floating-ui/popper library, for which I believed floating-ui/react-popper would be the official React wrapper for (as due to the description on repo)...

Is that also the same library with different name, or something that is in plan for the future?


As there is clearly need for more clarity on our side, could you please explain to us the differences and relations between these? 1. floating-ui/react-dom 2. floating-ui/react-popper + why we should consider using floating-ui/react-dom over floating-ui/react-popper?

We currently have implementation with desired behaviour done with floating-ui/react-popper and we're wondering what would be the main additional benefits from making the proposed change to floating-ui/react-dom :)

Are we supposed to understand your previous comment in a way that floating-ui/react-popper will be deprecated / not maintained anymore?

Thank you very much again!

@atomiks
Copy link

atomiks commented May 3, 2022

Could you please clarify what repository/project you refer to, when you mention ''react-popper not undergoing active development at this current time'' ?

Yes the react-popper package is being maintained but the main @popperjs/core library it uses isn't under active development (no new features, and bug fixes are mainly relied on by external contributors).

seems to be still using ReactDOM.render method

I'm not sure where you're seeing this, but it does not use render at all. It supported React 18 before react-popper did

As there is clearly need for more clarity on our side, could you please explain to us the differences and relations between these? 1. floating-ui/react-dom 2. floating-ui/react-popper + why we should consider using floating-ui/react-dom over floating-ui/react-popper?

The Motivation page linked explains why Floating UI over Popper. The React libraries are just wrappers over each, but react-popper is using @popperjs/core under the hood, whereas @floating-ui/react-dom uses @floating-ui/dom, the newer evolution of it.

I think the confusion here is that react-popper lives under floating-ui/react-popper, but it's because it rebranded from popperjs/react-popper.


TL;DR, use https://floating-ui.com/docs/react-dom. react-popper is from the old Popper project, it's not related to the new Floating UI packages, it just lives under the org because Popper rebranded to Floating UI and still needs somewhere to live. We released React 18 support for it because a ton of projects still use it, so it made sense. That doesn't mean it's actively developed though, just in low maintenance mode.

@floating-ui/popper will likely never exist because no one wants to write the legacy API wrapper, so you can ignore that

@martimalek
Copy link
Contributor

@JanHamara in the link you posted for floating-ui/react-popper there is a message in the README stating that the library is in maintenance mode (I don't know how I missed it before 😅).

Also I can see that @floating-ui/react-dom is supporting React 18.

Maybe it would be worth trying to implement the Popover with @floating-ui/react-dom before making a decision.

@JanHamara
Copy link
Contributor Author

I'm not sure where you're seeing this, but it does not use render at all. It supported React 18 before react-popper did

https://codesandbox.io/s/quizzical-water-b3dedw?file=/src/index.tsx

Right, I was looking at examples from the Docs page on non-dialog popover (link above) and seen it was still using render method from react-dom, that's where the confusion came from.

Okay, we will go ahead and test an implementation with @floating-ui/react-dom, then 👍

@JanHamara
Copy link
Contributor Author

I apologize in advance for quite a lengthy post!

UPDATE

After spending quite some time trying to implement the Popover component with our desired behaviour with @floating-ui/react-dom, and consulting the learnings with other participants on this thread, we have decided to go with @react-popper.

Following is the reasoning behind this decision:

Before listing reasons, why I think we should stick with @react-popper for now, I just want to state that @floating-ui is definitely a really interesting project and may be our go-to library in the future, however, due to our current needs and the following drawbacks of the lib, going with a @react-popper implementation is better for us at the moment:


1. Unnecessary complexity for our current needs

@floating-ui is a low-level library that aims to give you a completely fine-grained granular control over the anchored positioning and repositioning behaviour of whatever floating element you are building (be it tooltip, popover or modal, etc.), that includes detecting the overflow when resizing window or scrolling, updating the position of the floating element, flipping or shifting the element when needed, etc. (also showing / hiding the floating element with libs hooks for user interactions - only for React). That means none of this is enabled by default.

As react-dom docs state, @floating-ui/react-dom useFloating() hook only calculates the position once on render, or when the reference/floating elements changed — for example, the floating element gets mounted via conditional rendering. If the floating element lives in a different offsetParent context to the reference element, it will need to be updated while mounted to remain “anchored”. This includes scrolling and resizing the window or the elements themselves.

We need to configure the updating of floating element's position ourselves with autoUpdate method, and this is where confusion begins (after clicking the link to go to autoUpdate docs), but about this in next points...

To stay at the initial point first, we don’t need such a low-level granular library at the moment, as we don’t need an overly complicated solution where we would want to specify in detail the whole repositioning behaviour, we need a simpler out-of-the-box solution with a lib, that will take care of rendering the Popover content, automatically detecting the overflow on window.resize and on scroll, and repositioning (update, flip, shift) the Popover content as needed, something that we can implement quickly and it works.

@floating-ui docs even recommend this in the Tutorial part:
Screenshot 2022-05-17 at 14 29 37


Note: I would like to note here @atomiks , that this second paragraph is quite misleading. You recommend to use MUI, ChakraUI, etc. as ''All of the mentioned libraries use this library (formerly, Popper) under the hood.''

Reading ''use this library (formerly Popper)'' on @floating-ui documentation site, it quite clearly implies that they use @floating-ui under the hood, while in fact none of them use @floating-ui, they also still depend on @popperjs/core.

MUI - Source
Bootstrap - Source
Chakra UI - Source


Anyways, @react-popper enables the repositioning modifiers by default and takes care of detecting when we are scrolling or window resizes, repositioning the Popover content automatically if it cannot fit into viewport in its current position.

We can achieve this in no time with the basic working example in the documentation, allowing us to focus on what is important (whether that is building the different variants we need, focusing on styling or simply not spending too much time on the implementation of this component)

With @floating-ui it was not so easy, and this brings me to second point...


2. It's not quite ready for production yet - Documentation is not clear, at times even confusing and currently too unstable, it mutates almost on daily basis...

I think, the reasons why some top component libraries (like Chakra UI, Bootstrap, MUI, etc.) are not yet using @floating-ui library (even thought it’s a great project) are:

Without writing too much and boring you, I will try to explain how it may be confusing:

The docs begin with a pretty clear intro of the library concept and introduction of various packages / solutions (Native, React, React Native, Canvas)...

However, first page refers you to go straight to react-dom docs (if you’re developing React component) - this automatically assumes you already know / passed the concepts from Tutorial / computePositions chapter and not expect that all necessary concepts that you need to know for React implementation would indeed be in React part of documentation...

But anyways, let's assume we go through them anyways, mainly Tutorial which teaches you how to build a Tooltip with Native JS solution, which is completely different than solution you would have with React. I understand it may be just simpler to explain lib concepts with simplest solution, but this tutorial also introduces important utilities for React solutions such as autoUpdate, in different context, which already brings confusion about how this would be used in React solution (considering all lifecycle methods, etc.)


For example, say we start following steps in react-dom docs and we get to the part where we need to set up autoUpdate so we click the link to go to its documentation page:

Therefore, we move on to read autoUpdate docs page as we need to update the position automatically, but wait, this page again explains how to use this method with computePosition method, and autoUpdate method imported from floating-ui/dom package.

Screenshot 2022-05-17 at 15 09 33

But react-dom docs page has already told me to import useFloating wrapper hook and autoUpdate method from floating-ui/react-dom package.

Screenshot 2022-05-17 at 15 10 40

This brings a lot of confusion and questions:

  • Where do I import autoUpdate from then, and does it really matter if I import it from one package or another?
  • Do I now import computePosition too, in order to use in callback of autoUpdate, as autoUpdate docs tell me to? Or do I follow react-dom docs and believe that autoUpdate from floating-ui/react-dom is just a wrapper or identical to the autoUpdate method from floating-ui/dom?
  • And how do we then define the cleanup function without importing computePosition method too for React solution?
  • The autoUpdate docs page also mentions:

Screenshot 2022-05-17 at 15 13 45

  • How do I know if the requirement for ResizeObserver on autoUpdate doc page also applies to React solution if we got linked here? Does that update function refer to the same update function we are told to import on react-dom docs page?
  • Nevertheless, the fact that react-dom page links to the autoUpdate docs page, even though it explains a different solution is quite confusing.

Anyways, this is just an example to make a point, but I feel like the main issue would be to have the confidence we are using a stable solution when the documentation currently changes almost on daily basis.

One more example to demonstrate this:

whileElementsMounted, ‘’fully reactive callback prop to handle mounting/unmounting of the elements.’’ which we are told to use in conjunction with autoUpdate method for updating the position has only been added 9 days ago. → Commit #e52eea7


3. Lack of demo / working examples that would demonstrate implementation of a basic solution for react-dom

As also mentioned here: floating-ui/floating-ui#1677

There is no single clear and concise guide for developing a basic solution with React that would detect overflow and / or scrolling, and reposition (flip, shift) floating element when it cannot fit into the viewport.

For example, it would be extremely helpful to have demos of integration with react-dom solution, for the examples that you present on landing page.

Screenshot 2022-05-17 at 15 29 46

Yes, there are examples on react-dom-interactions page, but they demonstrate yet another solution using react-dom-interactions, while we are being told to use @floating-ui/react-dom with React DOM (especially if we don't need to incorporate custom logic).

I undestand what you are trying to say here:

Screenshot 2022-05-17 at 15 34 28

However, I think many teams will want to use your library as future-proof solution (as it obviously will bring many amazing advantages like minimal size, cross-platform compatibility, improved extensibility) for simple use-cases of Popover, Modal, Tooltip, etc. without ''having to reinvent the wheel''


4. Element position doesn't get computed when rescaling in iframe

Issue#1594

Making it currently not possible to use other tools that depend on it, like Storybook, for development/testing/documentation of Popover component.


5. We are not at v1 yet, it is not being used by many dependants

Umbrella - Roadmap to Floating UI v1

If we look at break down of dependents of popper.js and floating-ui/react-dom under @floating-ui repo, at the moment we can see:

popper.js: 2.1 million dependants
@floating-ui/react-dom: 373 dependants
@floating-ui/react-dom-interactions: 85 dependants


Conclusion

We are fully aware that popper.js will become obsolete over longer period of time. We also believe @floating-ui is a cool project and surely will be a go-to library for devs who want to have granular control over every single piece of positioning behaviour of floating element, but at this moment the lib and documentation mutate too often and are too unstable to be used for our Popover integration.

We will keep monitoring this library and when we see v1 released and / or some big players start using it, we will definitely reconsider moving to it.

Thank you for your insights @atomiks and we will be hoping to see v1 soon 👍

@atomiks
Copy link

atomiks commented May 17, 2022

@JanHamara thank you for the feedback especially regarding the docs. I keep trying to improve the docs to be better worded but unfortunately it seems it is still confusing. But your feedback was very valuable, thanks 👍

So to your points: it sounds like most of your concern is the library not being v1 yet and the documentation not being clear enough. Let me try to address these:

1..

It's not that complex, if you want to replicate Popper's "out of the box" behavior, all it takes is this:

import {useFloating, flip, shift, autoUpdate} from '@floating-ui/react-dom';

useFloating({
  middleware: [flip(), shift()],
  whileElementsMounted: autoUpdate,
});

The reason most of those libraries have not updated yet is because they can't update quickly at all. It takes a long time for them to move to the new dependency due to the breaking API changes.

Further, this "out of the box" behavior is inherently misleading because it still doesn't handle every single case. Popper is completely missing the size middleware which solves an important use case when no placements fit or it's too big for the viewport. You want to configure each one separately so you fully understand how the element is being positioned, it's difficult to do it for you automatically.

2..

This is largely because it's in v0. It's been in v0 to gather feedback on the new API but most of the breaking changes are small and can be updated in a couple lines or two by reading the changelog in releases section. I want to release v1 by the beginning of next month.

The documentation is mainly changing not because APIs change but to help improve the wording. The autoUpdate docs linking to the vanilla DOM docs is kind of just a shortcoming of how the packages wrap and depend on each other, but it looks like the wording needs to be better.

The changes are largely adding and improving things, not breaking them. e.g. whileElementsMounted simplifies autoUpdate usage but it was possible to use it before and didn't break anything.

Popper also suffered from these issues, it only provided out of the box behavior to simplify it but you still needed to read the vanilla modifiers docs to configure things. So it's basically the same imo.

3..

The API is designed to be less hard to adjust and more predictable. For instance, you start with simple anchored positioning and then add "visibility optimizations" through middleware afterward. This results in smaller bundles. Plus you don't need to write out a bunch of useState calls, so I think the useFloating wrapper is simpler than usePopper

4..

Popper also has this bug. It's not severe and almost no one will encounter it, as evidenced by it being a new bug and Popper also having it the whole time. The concern about iframes is not an issue because the element is outside of the iframe, plus it's scaled, the combo is extremely rare.

5..

popper.js is for v1, the v2 dependents are not listed. But yeah, obviously it has way more as it's existed for much longer 😄

To be honest I do think it's a mistake to use Popper in new projects if you are starting fresh.

Popper is larger, has less features (especially size which I think is a dealbreaker for going with Popper), and is not undergoing active development.

There are a bunch of bugs in Popper that have been fixed in Floating UI that you may encounter using it. Plus, it's about to hit v1 very soon and your concerns about it not being stable won't be an issue soon. Popper is only stable because it's missing a ton of features and wasn't being actively developed as its non-treeshakeable architecture prevented that.

So while it may look unstable because there is lots of activity on it, it is actually very stable and less buggy than Popper and only minor API changes are occurring. Plus new features, etc.

Another thing is Popper has poor TS support. There's zero autocomplete for the modifiers configuration. Floating UI is written in TS and has first class support

@nlopin
Copy link
Contributor

nlopin commented May 23, 2022

I'm closing this issue because the decision is taken. We will keep watching floating-ui for sure since this is the next iteration of popper

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
conversation needed Issues that may need to go to a broader conversation 📝 RFC Imporant proposals which impact the library
Development

No branches or pull requests

5 participants