react-native icon indicating copy to clipboard operation
react-native copied to clipboard

"VirtualizedLists should never be nested inside plain ScrollViews with the same orientation" error in console for FlatList/SectionList with scrollEnabled={false}

Open JeremyBradshaw7 opened this issue 4 years ago • 25 comments

Description

We use FlatList and SectionList in various sub-components which we embed into a master component in two ways:

  1. the components are in tabs of a tab control so each sub-component scrolls the FlatList or SectionList so has the benefit of using virtualization - we ensure scrollEnabled={true} in this case,
  2. the components are rendered one under the other in a single ScrollView - we ensure scrollEnabled={false} in this case,

We get the "VirtualizedLists should never be nested inside plain ScrollViews with the same orientation" console error repeatedly in the case of 2. I think when scrollEnabled={false} it should turn off virtualization and suppress this console error, and render the list internally using standard maps. Yes I can build my own wrapper components to do this, but I think the framework should do it naturally.

React Native version:

System: OS: macOS 11.3.1 CPU: (8) x64 Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz Memory: 388.60 MB / 16.00 GB Shell: 3.2.57 - /bin/bash Binaries: Node: 15.1.0 - /usr/local/bin/node Yarn: 1.19.1 - ~/.yarn/bin/yarn npm: 7.0.8 - /usr/local/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman Managers: CocoaPods: 1.10.1 - /usr/local/bin/pod SDKs: iOS SDK: Platforms: iOS 14.5, DriverKit 20.4, macOS 11.3, tvOS 14.5, watchOS 7.4 Android SDK: API Levels: 23, 28, 29, 30 Build Tools: 23.0.1, 23.0.2, 25.0.0, 25.0.1, 25.0.2, 26.0.2, 26.0.3, 27.0.0, 27.0.3, 28.0.2, 28.0.3, 29.0.2, 29.0.3 System Images: android-16 | Google APIs Intel x86 Atom, android-22 | Google APIs Intel x86 Atom, android-28 | Google APIs Intel x86 Atom, android-29 | Google APIs Intel x86 Atom, android-29 | Google APIs Intel x86 Atom_64 Android NDK: Not Found IDEs: Android Studio: 4.1 AI-201.8743.12.41.7199119 Xcode: 12.5/12E262 - /usr/bin/xcodebuild Languages: Java: 1.8.0_271 - /Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/bin/javac npmPackages: @react-native-community/cli: Not Found react: 17.0.1 => 17.0.1 react-native: 0.64.1 => 0.64.1 react-native-macos: Not Found npmGlobalPackages: react-native: Not Found

Steps To Reproduce

As per the description.

Expected Results

Would expect not to see the console error on a FlatList or SectionList which has scrollEnabled={false}.

JeremyBradshaw7 avatar Jun 10 '21 08:06 JeremyBradshaw7

Have you fixed it? I got it too. Everything worked well, but it shows errors in the console.

vuletuan avatar Jun 14 '21 03:06 vuletuan

I built a wrapper component to un-virtualize the list when scrollEnabled={false} to get around this, eg:

import React from 'react';
import { FlatListProps, FlatList as NativeFlatList, View } from 'react-native';

// Custom FlatList that doesnt try to virtualize when scrollEnabled={false}
// Do this to avoid "VirtualizedLists should never be nested inside plain ScrollViews with the same orientation" error in console
export default function FlatListWrapper<ItemT>(props: FlatListProps<ItemT>) {
  if (props.scrollEnabled === false) {
    return (
      <View style={props.style}>
        {!!props.ListHeaderComponent && !!props.data && props.data.length > 0 && props.ListHeaderComponent}
        {!!props.ListEmptyComponent && (!props.data || props.data.length === 0) && props.ListEmptyComponent}
        {!!props.data && props.data.map((item: any, index) => <View key={!!props.keyExtractor ? props.keyExtractor(item, index) : `${index}`}>
          {!!props.renderItem && props.renderItem({
            item, index, separators: {
              highlight: () => null,
              unhighlight: () => null,
              updateProps: (select: 'leading' | 'trailing', newProps: any) => null
            }
          })}
          {!!props.ItemSeparatorComponent && !!props.data && index < props.data.length - 1 && typeof props.ItemSeparatorComponent === 'function' && (props.ItemSeparatorComponent as any)()}
        </View>)}
        {!!props.ListFooterComponent && !!props.data && props.data.length > 0 && props.ListFooterComponent}
      </View>
    );
  }
  return <NativeFlatList {...props} />;
}

And for SectionList:

import React from 'react';
import { DefaultSectionT, SectionList as NativeSectionList, SectionListProps, View } from 'react-native';

// Custom SectionList that doesnt try to virtualize when scrollEnabled={false}
// Do this to avoid "VirtualizedLists should never be nested inside plain ScrollViews with the same orientation" error in console
export default function SectionListWrapper<ItemT = any, SectionT = DefaultSectionT>(props: SectionListProps<ItemT, SectionT>) {
  if (props.scrollEnabled === false) {
    return (
      <View style={props.style}>
        {!!props.ListHeaderComponent && !!props.sections && props.sections.length > 0 && props.ListHeaderComponent}
        {!!props.ListEmptyComponent && (!props.sections || props.sections.length === 0) && props.ListEmptyComponent}
        {!!props.sections && props.sections.map((item: any, index) => <View key={!!props.keyExtractor ? props.keyExtractor(item, index) : `${index}`}>
          {!!props.renderSectionHeader && props.renderSectionHeader({ section: item })}
          {!!props.SectionSeparatorComponent && !!props.sections && typeof props.SectionSeparatorComponent === 'function' && (props.SectionSeparatorComponent as any)()}
          {item.data.map((subitem: any, subindex) => <View key={`${subindex}`}>
            {!!props.renderItem && props.renderItem({
              item: subitem, section: item, index: subindex, separators: {
                highlight: () => null,
                unhighlight: () => null,
                updateProps: (select: 'leading' | 'trailing', newProps: any) => null
              }
            })}
            {!!props.ItemSeparatorComponent && !!item.data && subindex < item.data.length - 1 && typeof props.ItemSeparatorComponent === 'function' && (props.ItemSeparatorComponent as any)()}
          </View>)}
          {!!props.SectionSeparatorComponent && !!props.sections && typeof props.SectionSeparatorComponent === 'function' && (props.SectionSeparatorComponent as any)()}
          {!!props.renderSectionFooter && props.renderSectionFooter({ section: item })}
        </View>)}
        {!!props.ListFooterComponent && !!props.sections && props.sections.length > 0 && props.ListFooterComponent}
      </View>
    );
  }
  return <NativeSectionList {...props} />;
}

(this is TypeScript - I'm not sure about the separators implementation but this seems to circumvent the issue)

I'd still rather have the framework take care of this for me.

JeremyBradshaw7 avatar Jun 14 '21 07:06 JeremyBradshaw7

@beingArkReal raised issue tagged under question #28080, @CostachescuCristinel #27945 both issue are closed by the bot without any solution????

There is a question in StackOverflow https://stackoverflow.com/questions/67623952/error-virtualizedlists-should-never-be-nested-inside-plain-scrollviews-with-th

all of these not giving any clarity about this Error.

My case is

<Animated.ScrollView
  style={{ flex: 1 }}
  onScroll={Animated.event(
  [{ nativeEvent: { contentOffset: { y: scrollY } } }],
  { listener: (e) => handleScroll(e), useNativeDriver: false }
  )}
  nestedScrollEnabled={true}
>
  <Report />
  <ShortNav />
  <RequestedTripList setLoadIndent={setLoadIndent} />  // FlatList
</Animated.ScrollView>

How to handle this case?

KarthikeyanM28 avatar Sep 15 '21 12:09 KarthikeyanM28

@KarthikeyanM28 I have read a number of posts on the web, and kind of found some explanation for this warning. I am typing from what I can remember. I think I have found at some point someone pointing to the native source code of the scrollable component behind the ScrollView and FlatList components and explaining what happens, but I cannot find that post right now...

Before I start, I haven't found an official RN team answer as to why this warning and what the actual reason is for it.

Anyway, the story was something like this: The scrollable component source code that is behind the RN scrollables has a difficult time identifying whether it should steal and handle the touch events, or to let it pass-through. The problem is that this component was built similar to a singleton pattern, where one, and only one, of such components ever exists in the tree branch it is used in - you can have adjacent branches with this component, but no nested sub-branches.

The difficulty of the scrollable component when nesting multiple instances in sub-branches is that the instances will fight each other as to which one is eligible for stealing the touch events. Usually, this is resolved according to the event bubbling phase order (which flows bottom-up, child-to-parent), but the zIndex and the capture phase events (which flow top-down, parent-to-child, and before the bubbling phase) can screw this up. Not to mention that some other components (such as TouchableXXX) can also fight for the touch events (stack overflow has a number of posts on how to fix a ScrollView stealing touches from TouchableXXX childs).

Due to this flaw in the scrollable component, many issues arise:

  • the already mentioned scrollables stealing touches from touchables
  • nested TextInputs not being scrollable themselves
  • multiple scrollables scrolling at the same time and in the same direction (this one, I must say, it's interesting for a parallax effect, though, it is definitely an undesired side-effect).

There are also many component layout and sizing issues due to this. The nature of the scrollable components is to render dynamically sized content. This makes layout calculations hard for RN since it has to continuously bounce between parents and childs to calculate the layouts, until all resolved dimensions will stabilize. { height: "100%" } (or width for horizontal scrollables) or { flexGrow: 1 } can make for a really troubling case where the scrollable content size grows, then child's size grows as well, causing the content size to grow again, and this process repeating infinitely until the app crashes.

The issue is somewhat non-conflicting when limiting nested scrollables to 2 instances and using different scrollable directions. Scrollables have a gesture distance activation threshold (if I remember correctly, there must be a delta of +/-10dp) in the direction observed before the scrollable will deem itself eligible for stealing the touch events. Since the horizontal and vertical directions look for deltas on different axes, they do not interfere with each other that much.
This explains the ... with the same orientation part of the warning. However, I have had a number of instances where both scrolling directions would activate at the same time, making it difficult for the user to follow the intended touch gesture. Again, nest one more with the same orientation, and you're back to the issues presented above.

As far as I could conclude from my findings on the web, the React Native team solution was... well, this warning.
They did not enforce this rule to completely prevent you from nesting scrollables, at any depth and with whichever orientation you desire, but they are also not supporting use cases of nested scrollables with the same orientation.
The consequence is that reporting a bug for misbehavior in these cases will not be their responsibility - it is not supported. Also, I do not think that they have any plans at this time to write a proper scrollable component that would behave correctly.

It is worth mentioning however that nesting scrollables with a different orientation is a supported use case, as also confirmed by the nestedScrollEnabled prop required to enable nested scrolling on Android (now enabled by default). Also, if I am not mistaken, the problem of this scrollable component also had to do with differences in scroll handling and behavior between iOS and Android.

So, if you're wondering about possible solutions:

  • LogBox.ignoreLogs(['VirtualizedLists should never be nested']);
    Workaround. And then you should test a lot between iOS and Android to make sure that the behavior is consistent and does not fall apart.
  • As the warning says, do not nest scrollables with the same orientation (and, I would add, do not do this deeper that 1 parent and 1 child). Try to stay as much as possible within the RN limitations, and use the available scrollable components and their props to your advantage:
    • use ScrollView for randomly sized and arranged components
    • use FlatList for homogeneous components, but without visual grouping of sort
    • use SectionList for homogeneous components organized by groups
    • use VirtualizedList for a combination of ScrollView/FlatList/SectionList -like behavior, but where you actually need a lot of control over the item rendering and size calculation
    • Use ListHeaderComponent and ListFooterComponent to render dynamic items at the beginning and end of FlatList or SectionList. These components do not have to be the same form or size as the rest of the items rendered in the list.
    • Disable scroll when the content size is smaller than the container size, to prevent scroll events from interfering with each other. This can possibly improve the user experience as the scroll will stay fixed as much as possible
class AutoDisableScrollable extends React.PureComponent {
	state = { container: 0, content: 0 };

	onLayout = (layoutEvent) => {
		const { nativeEvent: { layout: { width, height } } } = layoutEvent;
		this.setSize("container", width, height, () => {
			if (typeof (this.props.onLayout) === "function") {
				this.props.onLayout(layoutEvent);
			}
		});
	}

	onContentSizeChange = (width, height) => {
		this.setSize("content", width, height, () => {
			if (typeof (this.props.onLayout) === "function") {
				this.props.onContentSizeChange(width, height);
			}
		});
	}

	setSize = (ofItem, width, height, callback) => {
		const currentSize = this.state[ofItem];
		const newSize = (this.props.horizontal !== true) ? height : width;

		if (newSize !== currentSize) {
			this.setState({ [ofItem]: newSize }, callback);
		} else {
			callback();
		}
	}

	render = () => {
		const {
			__ref, // Use this instead of the "ref" prop
			ScrollableComponent, // ScrollView, FlatList, SectionList, VirtualizedList
			scrollEnabled, bounces, onLayout, onContentSizeChange, children, ...props
		} = this.props;
		
		const { container, content } = this.state;
		const __scrollEnabled = ((scrollEnabled !== false) && (content > container));
		const __bounces = ((bounces !== false) && (__scrollEnabled === true));

		return <ScrollableComponent
			ref={__ref}
			scrollEnabled={__scrollEnabled}
			bounces={__bounces}
			onLayout={this.onLayout}
			onContentSizeChange={this.onContentSizeChange}
			{...props}
		>
			{children}
		</ScrollableComponent>;
	}
}
<AutoDisableScrollable
    ScrollableComponent={ScrollView}
    style={{ flex: 1 }}
    ...
>
    ...
</AutoDisableScrollable>

I hope that this wall of text helps you and others to get scrollables working correctly. I do have ideas for a custom scrollable component that uses Animated.Views and a PanResponder to handle touches and "scroll", however, I have not materialized this yet. In case you want to dig into this, a container View would use a PanResponder to respond to touch events, and in given conditions, would accept handling touches and update the translateX / translateY props of a child content container Animated.View to "scroll" the items (the approach is similar to how scrollables in RN are built).

CostachescuCristinel avatar Sep 15 '21 15:09 CostachescuCristinel

I am receiving the same error

Zakyyy avatar Oct 24 '21 12:10 Zakyyy

1 same issue

tunm1228 avatar Nov 02 '21 07:11 tunm1228

same issue for me

edritech93 avatar Dec 17 '21 03:12 edritech93

Hello,

I have the same error, and the proposed solutions do not suit me very well... Is it possible to disable this error when scrollEnabled={false} ? If the scroll is disabled, I don't see how it can be a problem to use a FlatList in a ScrollView.

fkeloks avatar Jan 14 '22 14:01 fkeloks

any solution this error occurs only after 0.61 to 0.64 upgrade till it was fine now I cant change to each and every pages

dhairyasenjaliya avatar Feb 02 '22 05:02 dhairyasenjaliya

I personnaly changed ScrollView to a FlatList, which brought me to the same result:

<FlatList
	data={[{}]}
	keyExtractor={() => null}
	renderItem={() => <>{children}</>}
/>

NastuzziSamy avatar Feb 02 '22 22:02 NastuzziSamy

I have found a solution for this. You can wrap your Component inside a frame and pass it to renderItem in your FlatList. Then you can use as many FlatList inside FlatList but eliminate the use of ScrollView , if you don't wnat to see this warning again.

ASKNJ avatar Feb 20 '22 21:02 ASKNJ

Can you give us the sample code?

vuletuanbt avatar Feb 27 '22 05:02 vuletuanbt

<FlatList data={data} renderItem={renderItem} />

mdrajonhossain avatar Mar 11 '22 05:03 mdrajonhossain

Same error here, using a scrollview as parent view, and nesting a SelectBox from react-native-multi-selectbox package. Any update on this?

Update 1: I was able to solve this by adding listOptionProps={{nestedScrollEnabled: true}} like this:

<ScrollView>
  <SelectBox
    label="Select single"
    options={serverData}
    listOptionProps={{nestedScrollEnabled: true}}
    value={input.elementSelected}
    onChange={event =>
      inputHandlerLang('elementSelected', event, key)
    }
    hideInputFilter={false}
  />
</ScrollView>

the error still present but scrolling within SelectBox works as well as within the parent scrollview. I also do have to suppress the error with LogBox. I don't know if there are any drawbacks to this but I'll try to test this more.

anassbenayed avatar May 11 '22 21:05 anassbenayed

Directly find the source code and comment out the console error

path: react-native/Libraries/Lists/VirtualizedList.js line: 1135

if (__DEV__) {
      ret = (
        <ScrollView.Context.Consumer>
          {scrollContext => {
            if (
              scrollContext != null &&
              !scrollContext.horizontal ===
                !horizontalOrDefault(this.props.horizontal) &&
              !this._hasWarned.nesting &&
              this.context == null
            ) {
              // TODO (T46547044): use React.warn once 16.9 is sync'd: https://github.com/facebook/react/pull/15170
              // console.error(
              //   'VirtualizedLists should never be nested inside plain ScrollViews with the same ' +
              //     'orientation because it can break windowing and other functionality - use another ' +
              //     'VirtualizedList-backed container instead.',
              // );
              this._hasWarned.nesting = true;
            }
            return innerRet;
          }}
        </ScrollView.Context.Consumer>
      );
    }

GuoguoDad avatar Jun 09 '22 08:06 GuoguoDad

Any update on this issue? Inserting one or multiple FlatLists inside a ScrollView, even with scrolling inside the lists being disabled, sounds like a common thing to me. It avoids having to .map() our data lists ourselves and brings a few other benefits from the FlatList component. And I don't think "remove the error from the source code yourself" is a correct solution either 👀

ZRunner avatar Aug 03 '22 12:08 ZRunner

Any update on this issue? Inserting one or multiple FlatLists inside a ScrollView, even with scrolling inside the lists being disabled, sounds like a common thing to me. It avoids having to .map() our data lists ourselves and brings a few other benefits from the FlatList component. And I don't think "remove the error from the source code yourself" is a correct solution either 👀

Before version 0.63, it was only a warning, and then it became an error. If you have a better way, I am not willing to change the source code

GuoguoDad avatar Aug 04 '22 01:08 GuoguoDad

A solution for me was to create a custom FlatList component, something like this

import React from 'react'
import { FlatList, FlatListProps } from 'react-native'

import { uniqueId } from 'lodash'

/** This component was created to avoid the Warning about nested virtualized lists when nesting a flatlist inside a scrollview */

const FlatListScrollView = <T extends any>({ children, ...props }: React.PropsWithChildren<Partial<FlatListProps<T>>>) => {
  return (
    <FlatList
      data={[undefined] as any}
      showsVerticalScrollIndicator={false}
      renderItem={() => <React.Fragment>{children}</React.Fragment>}
      keyExtractor={(_, index) => uniqueId('flat-list-scroll-view-element-' + index)}
      initialNumToRender={1}
      maxToRenderPerBatch={1}
      {...props}
    />
  )
}

export default FlatListScrollView

sgolban avatar Nov 25 '22 20:11 sgolban

As far as I see it this is fixed in RN 0.71 Changelog

Plain view of the changelog with line reference: https://github.com/facebook/react-native/blob/main/CHANGELOG.md?plain=1#L63

jensneuber avatar Jan 19 '23 10:01 jensneuber

Yes the fix is here: https://github.com/facebook/react-native/commit/62f83a9fad027ef0ed808f7e34973bb01cdf10e9

One-liner to just supress the error when scrollEnabled is false. Which is sensible. I think this also means we're safe to ignore this error if we know scrollEnabled is false, even before we upgrade to RN 0.71

thanks for the fix @annepham25

tsheaff avatar Feb 02 '23 23:02 tsheaff

Screenshot 2023-02-02 at 13 39 28

here's a more detailed view of which release tags this commit is included in

I'm using Expo which doesn't support 0.71 yet, so I can't get on the latest yet.

tsheaff avatar Feb 02 '23 23:02 tsheaff

Solution: do(scolled Enabled = false) child component

dibenduCfcs avatar Mar 24 '23 05:03 dibenduCfcs

🎖 Simple Solution To Fix It: 100% works 🤩

1- Add nestedScrollEnabled={true} to ScrollView 2- Add scrollEnabled={false} to FlatList

<ScrollView nestedScrollEnabled={true} >
     <FlatList
            scrollEnabled={false}
            ...
        />
 </ScrollView>

Mahdi-Farahani avatar Oct 02 '23 06:10 Mahdi-Farahani

Screenshot 2023-02-02 at 13 39 28

here's a more detailed view of which release tags this commit is included in

I'm using Expo which doesn't support 0.71 yet, so I can't get on the latest yet.

I'm on Expo SDK48 which support RN 0.71.13 and error is still shown. I assume the fix to be included in SDK49 but not go there yet!

butri71 avatar Oct 07 '23 10:10 butri71

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

react-native-bot avatar Sep 18 '24 05:09 react-native-bot

This issue was closed because it has been stalled for 7 days with no activity.

react-native-bot avatar Sep 25 '24 05:09 react-native-bot

How is adding scrollEnabled={false} to a nested FlatList a solution? Don't we want to use nested flat lists, as they are, for things like select boxes?

Furthermore, I've seen sooo many people recommend scrollEnabled={false} on a FlatList, then promote wrapping it in a ScrollView which I thought was bad practice, especially when you want to start listening to scroll events, or add RefreshControl into the mix? You want to do those things directly on the FlatList.

The only solution I can see, so far, (which also seems strange) is rendering all of an app's main content in a FlatList as one item, seeing as they are virtualised and can be nested within each other safely.

Or a new prop for ScrollView that allows for nested virtualized lists. It's up to the developer to use responsibly. ScrollView is super handy for humble pages (like a settings screen) that need to overflow a little on the y-axis, and there's typically not much content in them anyway.

We need a legit solution here.

Vimiso avatar Nov 19 '24 02:11 Vimiso