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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Tabs] Are not working with React.Suspense #14077

Closed
2 tasks done
HaNdTriX opened this issue Jan 3, 2019 · 20 comments
Closed
2 tasks done

[Tabs] Are not working with React.Suspense #14077

HaNdTriX opened this issue Jan 3, 2019 · 20 comments
Labels
bug 馃悰 Something doesn't work component: tabs This is the name of the generic UI component, not the React module! external dependency Blocked by external dependency, we can鈥檛 do anything about it

Comments

@HaNdTriX
Copy link
Contributor

HaNdTriX commented Jan 3, 2019

  • This is not a v0.x issue.
  • I have searched the issues of this repository and believe that this is not a duplicate.

Expected Behavior 馃

Tabs should mount correctly when using <React.Suspense>

bildschirmfoto 2019-01-03 um 16 24 37

Current Behavior 馃槸

Tabs do not render correctly when using <React.Suspense>.

  • scrollbars are visible
  • <TabIndicator /> is missing

bildschirmfoto 2019-01-03 um 16 24 17

Steps to Reproduce 馃暪

  1. Open https://codesandbox.io/s/p5186w26nm

Context 馃敠

I believe the issue here is that componentDidMount is called before the Component is visible.
(<React.Suspense> renders the Tabs to a hidden div).
This causes all layout methods (updateIndicatorState, updateScrollButtonState) to not work properly.

bildschirmfoto 2019-01-03 um 16 27 23

Your Environment 馃寧

Tech Version
Material-UI v3.8.1
React v16.7.0-alpha.2
@HaNdTriX
Copy link
Contributor Author

HaNdTriX commented Jan 3, 2019

I believe this issue affects all layout methods used in componentDidMount and @material-ui (not only Tabs).

@joshwooding
Copy link
Member

This is due to how the scrollbar size is calculated
https://github.com/mui-org/material-ui/blob/0d8e8678bc3eb7f832238b5b8e4c584450680882/packages/material-ui/src/Tabs/ScrollbarSize.js#L57

when an element isn't visible it's offsetHeight and clientHeight are 0 which breaks the calculation as it's listening on window resizing

@HaNdTriX
Copy link
Contributor Author

HaNdTriX commented Jan 4, 2019

@joshwooding I thought so. We might need another lifecycle hook to start these calculations as soon as this component is visible.

@oliviertassinari
Copy link
Member

oliviertassinari commented Jan 5, 2019

Oh boy! React Suspense, what have you done! 馃槅
Regarding the scrollbar height issue, one possible workaround is to use the Portal component:

--- a/packages/material-ui/src/Tabs/ScrollbarSize.js
+++ b/packages/material-ui/src/Tabs/ScrollbarSize.js
@@ -2,6 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import EventListener from 'react-event-listener';
 import debounce from 'debounce'; // < 1kb payload overhead when lodash/debounce is > 3kb.
+import Portal from '../Portal';

 const styles = {
   width: 90,
@@ -34,15 +35,15 @@ class ScrollbarSize extends React.Component {
     }
   }

-  componentDidMount() {
-    this.setMeasurements();
-    this.props.onChange(this.scrollbarHeight);
-  }
-
   componentWillUnmount() {
     this.handleResize.clear();
   }

+  handleRendered = () => {
+    this.setMeasurements();
+    this.props.onChange(this.scrollbarHeight);
+  };
-
   componentWillUnmount() {
     this.handleResize.clear();
   }

+  handleRendered = () => {
+    this.setMeasurements();
+    this.props.onChange(this.scrollbarHeight);
+  };
+
   handleRef = ref => {
     this.nodeRef = ref;
   };
@@ -61,7 +62,9 @@ class ScrollbarSize extends React.Component {
     return (
       <React.Fragment>
         <EventListener target="window" onResize={this.handleResize} />
-        <div style={styles} ref={this.handleRef} />
+        <Portal onRendered={this.handleRendered}>
+          <div style={styles} ref={this.handleRef} />
+        </Portal>
       </React.Fragment>
     );
   }

Now, regarding the React lifecycles, I can't find anything we can leverage to trigger the tab indicator and scrollbar button display. I think that we should open an issue on React side. I doubt we are the only one with the problem. I think that React should provide an API to notify the components that the display: none style was removed.

@oliviertassinari
Copy link
Member

oliviertassinari commented Jan 5, 2019

I have opened an issue on React side: facebook/react#14536. The Portal is really a dirty hack that only solves 1/3 of the issue.

@oliviertassinari oliviertassinari added external dependency Blocked by external dependency, we can鈥檛 do anything about it component: tabs This is the name of the generic UI component, not the React module! labels Jan 5, 2019
@oliviertassinari
Copy link
Member

@joshwooding has found in #14181 that we can potentially use the IntersectionObserver API.

@eps1lon
Copy link
Member

eps1lon commented Jan 15, 2019

@joshwooding has found in #14181 that we can potentially use the IntersectionObserver API.

I like this API but https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Browser_compatibility

@eps1lon
Copy link
Member

eps1lon commented Jan 15, 2019

Disclaimer: Don't use this in production.

Fixed demo: https://codesandbox.io/s/6znx30q23w

There are two heuristics for determining if a tree is suspended:

  1. The fallback is not unmounted yet i.e. the tree is suspended by default and only "unsuspend" in cWU
  2. A DOM node in the tree without inline styles changes its style (checked via MutationObserver

The first point will not work if no fallback is rendered i.e. maxDuration is not reached. The second point is all over the place since I tried to make it as memory safe as possible. It's also quite wasteful since I simply remount the Tabs once I think the tree is not supsended.

@joshwooding
Copy link
Member

I was looking at MutationObserver the only thing I don't like about is that it uses polling and although the fix won't be bad for Tabs with Textarea I suspect the performance implications to be greater

@eps1lon
Copy link
Member

eps1lon commented Jan 15, 2019

I was looking at MutationObserver the only thing I don't like about is that it uses polling and although the fix won't be bad for Tabs with Textarea I suspect the performance implications to be greater

None of this (including react) is production ready so I think you're getting ahead of yourself if you're already thinking about performance.

@joshwooding
Copy link
Member

joshwooding commented Jan 15, 2019

I was looking at MutationObserver the only thing I don't like about is that it uses polling and although the fix won't be bad for Tabs with Textarea I suspect the performance implications to be greater

None of this (including react) is production ready so I think you're getting ahead of yourself if you're already thinking about performance.

I do realise the code isn鈥檛 production ready. I was just adding my thoughts and just listing the potential downsides I had read about. 馃檪

@eps1lon
Copy link
Member

eps1lon commented Jan 16, 2019

[...] I had read about.

Speaking about it: Do you have sources for this? Quick google search did not yield anything beyond specific problems on stackoverflow about bad performance of MutationObserver. Did not find anything about polling.

@Jessidhia
Copy link

The entire point of using IntersectionObserver or MutationObserver is to eliminate polling. Polling might only be needed if you're having to polyfill it.

@joshwooding
Copy link
Member

joshwooding commented Jan 16, 2019

@eps1lon I can鈥檛 remember where I read it now. Maybe I misread something

@HaNdTriX
Copy link
Contributor Author

馃か You can also use CSS animation events to detect the visibility of a Dom node. Nevertheless I believe this issue needs to be fixed by react core.

@Eldraed
Copy link

Eldraed commented Apr 17, 2019

Hello,
Just for information, i'm also having trouble with the "outlined" variant of TextField due to this portion of code :
https://github.com/mui-org/material-ui/blob/41920170e5d631ec3d1ac686b7bdf0e31befc3e6/packages/material-ui/src/TextField/TextField.js#L97-L103
Like Tabs, width is calculated during the fallback of my Suspense Loader, so width = 0px !

In result, the line pass under the label :
image

@oliviertassinari oliviertassinari added the bug 馃悰 Something doesn't work label Apr 17, 2019
@eps1lon
Copy link
Member

eps1lon commented Apr 17, 2019

@Eldraed This is a common issue with Suspense in sync mode. React will still mount trees in an inconsistent state and just display: none; the components that are not suspended.

What you can do for now is remount the TextField if your placeholder unmounts with a key that changes depending on the mount state of your placeholder.

If you can you could also put the suspense tree inside React.unstable_ConcurrentMode which doesn't mount inconsistent trees.

See facebook/react#14536 for more information.

@Eldraed
Copy link

Eldraed commented Apr 17, 2019

Thanks for the reply @eps1lon

I tested Concurrent Mode before posting earlier, i didn't mention it.
It works well, label is good but Concurrent mode comes with Strict mode... I was looking on using Strict mode weeks ago but some libraries doesn't works well with it for now (like Apollo). So i left that aside for now, and was hoping to get updates soon on it.

So, I'll rather look at your first option :)

@oliviertassinari oliviertassinari changed the title Tabs are not working with React.Suspense [Tabs] Are not working with React.Suspense Aug 22, 2020
@gaearon
Copy link

gaearon commented Jun 16, 2021

This is fixed with React 18 alpha.

17: https://codesandbox.io/s/material-ui-tabs-suspense-bug-forked-4n89l?file=/src/index.js
18 alpha with createRoot: https://codesandbox.io/s/material-ui-tabs-suspense-bug-forked-lw409?file=/src/index.js

Concurrent mode comes with Strict mode

Not anymore! (Also, it's not a "mode" anymore either.)

@eps1lon
Copy link
Member

eps1lon commented Jul 26, 2021

Closing since the underlying React issue has been closed as well.

@eps1lon eps1lon closed this as completed Jul 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 馃悰 Something doesn't work component: tabs This is the name of the generic UI component, not the React module! external dependency Blocked by external dependency, we can鈥檛 do anything about it
Projects
None yet
Development

No branches or pull requests

7 participants