Open Kimotn opened 6 years ago
Pass your own ScrollView with the header added already. That would be an easy way. We don't support it by default because measuring header will cause an extra frame skip. Those who want it should do it themselves and then offset its height using distanceFromWindow
@naqvitalha I'm using an externalScrollView
to render a header like this
{renderHeader && renderHeader()}
<ScrollViewer
ref="refS"
{...this.props}
onSizeChanged={() => {
/* Do nothing */
}}
/>
The problem here is that the header is always on top of the list while scrolling.
What did you mean when you said offset the height using distanceFromWindow
?
I added one by going into: node_modules\recyclerlistview\dist\reactnative\platform\reactnative\scrollcomponent\ScrollComponent.js
And adding the following line as line 53 (after "} }, ")
this.props.renderHeader ? this.props.renderHeader() : null,
Maybe not good practise but has worked for me so far. This means the header 'scrolls away', instead of always being fixed at the top of the list.
@tafelito
What did you mean when you said offset the height using
distanceFromWindow
?
It's a prop of RecyclerListView that you can use, see here.
Note that your other issue ("The problem here is that the header is always on top of the list while scrolling.") is caused by rendering your header outside of the ScrollView - you want it to be IN the ScrollView.
I also had this issue and my solution was to add the header as the first item of the list and gave it its own ViewType, and it worked perfectly.
@evert-smit distanceFromWindow won't work in this case, even if I add the header inside the ScrollView. The solution was to do something similar to what @Komeyl94 did. It's not nice, and it's a hack, and you have to know the height of the header beforehand
I also had this issue and my solution was to add the header as the first item of the list and gave it its own ViewType, and it worked perfectly.
you can make example ?
I had to solve this problem as well so I thought it would be worth putting an annotated example of how you can do this in a way that is compatible will what RecyclerListView expects.
If you replace Header with whatever component you need you'll get a RecyclerListView with that header that exists within the confines of the list
import PropTypes from 'prop-types';
import React from 'react';
import { RecyclerListView } from 'recyclerlistview';
import { ScrollView, StyleSheet, View } from 'react-native';
// Create header component with gap
const style = StyleSheet.create({
header: {
marginTop: 16,
},
});
const Header = () => <View style={style.header} />;
// Use forwardRef to wrap a ScrollView that has the Header before the rest of the
// Children are rendered. forwardRef is there because RecyclerListView expects whatever you
// pass it as the externalScrollView to have the same methods available as ScrollView
const ScrollViewWithHeader = React.forwardRef(({ children, ...props }, ref) => {
return <ScrollView
ref={ref}
{...props}
>
<Header />
{children}
</ScrollView>;
});
// Overriding PropType because it doesn't expect a forwardRef response even though that
// works without issue
RecyclerListView.propTypes.externalScrollView = PropTypes.object;
// Use the headered scroll view to underly the RecyclerList
const RecyclerListViewWithHeader = (props) => {
return <RecyclerListView
externalScrollView={ScrollViewWithHeader}
{...props} />;
};
export default RecyclerListViewWithHeader;
I had to solve this problem as well so I thought it would be worth putting an annotated example of how you can do this in a way that is compatible will what RecyclerListView expects.
If you replace Header with whatever component you need you'll get a RecyclerListView with that header that exists within the confines of the list
import PropTypes from 'prop-types'; import React from 'react'; import { RecyclerListView } from 'recyclerlistview'; import { ScrollView, StyleSheet, View } from 'react-native'; // Create header component with gap const style = StyleSheet.create({ header: { marginTop: 16, }, }); const Header = () => <View style={style.header} />; // Use forwardRef to wrap a ScrollView that has the Header before the rest of the // Children are rendered. forwardRef is there because RecyclerListView expects whatever you // pass it as the externalScrollView to have the same methods available as ScrollView const ScrollViewWithHeader = React.forwardRef(({ children, ...props }, ref) => { return <ScrollView ref={ref} {...props} > <Header /> {children} </ScrollView>; }); // Overriding PropType because it doesn't expect a forwardRef response even though that // works without issue RecyclerListView.propTypes.externalScrollView = PropTypes.object; // Use the headered scroll view to underly the RecyclerList const RecyclerListViewWithHeader = (props) => { return <RecyclerListView externalScrollView={ScrollViewWithHeader} {...props} />; }; export default RecyclerListViewWithHeader;
I tried you implementation and it works ok, but I noticed one problem, by scrolling down rows rendering ok, and by scrolling up row is rendered when I reach half of the row, so I can see white row until I reach half of it. Then it is populated. When I remove Header it works normally. Do you have solution for this? This is how Recycle view looks like :
<RecyclerListView style={{paddingTop: 8, paddingBottom: 60}} contentContainerStyle={{ alignSelf: 'center', }} externalScrollView={this.externalScrollView} forceNonDeterministicRendering={true} canChangeSize={true} dataProvider={this.state.dataProvider} rowRenderer={this.rowRenderer} layoutProvider={this.layoutProvider} refreshing={true} onRefresh={this.onRefresh} keyExtractor={item => item.id.toString()} onScroll={this._scrolled.bind(this)} extendedState={this.state} onVisibleIndexesChanged={this.onViewableItemsChanged} onEndReached={this.onEndReached} onEndReachedThreshold={0.9} viewabilityConfig={this.viewabilityConfig} snapToAlignment={"center"} />
@naqvitalha @evert-smit distanceFromWindow
would only be a requirement for web, right? Or would this be necessary in a react-native project?
I've implemented a header using the external scrollview approach, however I get some weird scrolling behaviour/jumping in the list when we've scrolled near the end of it
The issue is still present, haven't found a decent example how to implement a header. Passing it as a first item doesn't help, because if I have an action in header which triggers new data load, header also gets rerendered which is annoying. By the way, passing my own header also rerenders the header.
The issue is still present, haven't found a decent example how to implement a header. Passing it as a first item doesn't help, because if I have an action in header which triggers new data load, header also gets rerendered which is annoying. By the way, passing my own header also rerenders the header.
I actually have the opposite issue, because the header doesn't consistently re-render, sometimes it doesn't re-render when I need it to when the state has updated. So sometimes when I click a dropdown menu I've implemented in the header, the dropdown menu doesn't re-render to reflect the selected option, even if the list rows do get updated...
@abduraufsherkulov @chanmathewrbc I had the same problems with re-rendering the header both when I didn't want and also not re-rendering when I want.
What I did to fix my issues:
1) Use the custom ScrollView
listed above, but wrap it in useMemo
. Give it a dependency array if you need to control when it should re-render or not. Note: This is only for the outer ScrollView component, not for the header
2) If you do the above with an empty dependency array, your header won't re-render at all. Your first instinct might be to add your header component to the dependency array of the above useMemo
. If you do that, your header will re-render, but so will your entire scroll view and you will always be scrolled instantly to the top.
3) Fortunately, you can pass props with scrollViewProps
. These props bypass the useMemo
and will always be updated, regardless of the scroll view.
So for example
const ScrollViewWithHeader = React.useMemo(() => React.forwardRef(({ children, ...props }, ref) => {
return <ScrollView
ref={ref}
{...props}
>
{props.headerComponent}
{children}
</ScrollView>;
}), []); // Pass dependencies for ScrollView here
const RecyclerListViewWithHeader = (props) => {
return <RecyclerListView
externalScrollView={ScrollViewWithHeader}
scrollViewProps={
{ headerComponent: Header }
}
{...props} />;
};
Now, your ScrollView
is completely controllable and will re-render only when you want to. At the same time, your headerComponent
can be controlled separately. If you want to control when it re-renders, you can wrap it in its own useMemo
.
This fixed my scenario where I needed to have a custom header component that only re-rendered certain times, while leaving the rest of my list intact. I gave my ScrollView
an empty dependency array and just re-rendered my headerComponent
.
Hope this helps!
{...props} />;
Thank you for sharing. Can you please also share how your layoutProvider looks and how you accounted for header height? Does this support multiple headers in between data?
@ehsan6sha Header height should be automatically considered since it is rendered on top of the list-view. If you only have a header and then your list items, regardless of the header height, it should work.
As for multiple headers in between data, it probably will need more customisation. I haven't had this use case so I am not sure.
My layout provider is pretty basic
const [layoutProvider] = useState(
new LayoutProvider(
() => {
return 0;
},
(type, dim) => {
dim.width = Dimensions.get("screen").width;
dim.height = estimatedLayoutHeight; // Comes from parent or defaults to 80
}
)
);
@dariuscosden the error scrolling up and down with half white cell item is persist. @ilicnenad have you found the solution yet?
Very late to the party, I've written a generic RecyclerListViewWithHeader component that builds on @dariuscosden's work and also addresses the whitespace issue raised by @dzpt and @ilicnenad by applying a window correction to the RLV.
The only limitation is that the header has to have a fixed height. It has to be pre-determined (rather than measured on rendering the header) because updating the RLV windowCorrectionConfig once it's rendered has no effect.
It could be worth adding something about this to the docs, given it's a very common use-case, and the only info on it is all buried in this issue.
import React, { forwardRef } from "react";
import { ScrollView } from "react-native";
import { RecyclerListView } from "recyclerlistview";
import type {
RecyclerListViewWithHeaderProps,
ScrollViewWithHeaderProps,
} from "~/components/general/RecyclerListViewWithHeader/types";
// this component makes it possible to render a scrollable header above an RLV
// https://github.com/Flipkart/recyclerlistview/issues/233
// note that the header height needs to be fixed so that RLV window can be shifted correctly
const ScrollViewWithHeader = forwardRef<ScrollView, ScrollViewWithHeaderProps>(
(props, ref) => {
const { children, renderHeader } = props;
return (
<ScrollView ref={ref} {...props}>
{renderHeader}
{children}
</ScrollView>
);
}
);
const RecyclerListViewWithHeader = (props: RecyclerListViewWithHeaderProps) => {
const { headerHeight, renderHeader } = props;
return (
<RecyclerListView
// @ts-expect-error RLV typing of externalScrollView is overly fussy
externalScrollView={ScrollViewWithHeader}
scrollViewProps={{ renderHeader }}
// need to adjust the window for the header otherwise we'll see blank space
windowCorrectionConfig={{
value: {
windowShift: -headerHeight,
startCorrection: 0,
endCorrection: 0,
},
}}
{...props}
/>
);
};
export default React.memo(RecyclerListViewWithHeader);
Add header to Recyclerlistview same this : https://medium.com/appandflow/react-native-scrollview-animated-header-10a18cb9469e Thanks