- EnhancedScrollView
- StickyHeaderView
- AnchorView and createAnchorHandle
- LazyList
- HideOffscreen
- useScrollViewRef
- useAnimatedScrollValue
- useScrollEvents
- useGetPositionInScrollView
This component is the basis for this library. It wraps an animated ScrollView, and accepts all the props of ScrollView as well. The other components and hooks of this library will only work properly when used inside an EnhancedScrollView.
EnhancedScrollView has two props besides ScrollView's props:
| Prop Name | Type | Description | Required |
|---|---|---|---|
scrollViewRef |
RefObject<{ getNode(): ScrollView }> |
A ref object that will be assigned a ref to the EnhancedScrollView's internal Animated.ScrollView. Note that you need to call its getNode() method to get a ref to the ScrollView inside that. |
No |
animatedYTracker |
Animated.Value |
An animated value that will be set to the scroll amount of the EnhancedScrollView |
No |
import { EnhancedScrollView } from "react-native-scrollables";
function MyScrollableView() {
return (
<EnhancedScrollView>
<Text>Hello, world! This is inside a scrollable view!</Text>
<ComponentThatUsesAReactNativeScrollablesHook />
</EnhancedScrollView>
);
}A view that's given a "sticky header element", which starts out positioned above the StickyHeaderView's children, but then sticks to the top of the screen as the user scrolls down the nearest parent EnhancedScrollView.
| Prop Name | Type | Description | Required |
|---|---|---|---|
stickyHeaderElement |
ReactNode |
A React element to render above the children of StickyHeaderView, that then sticks to the top of the screen as the user scrolls down. |
Yes |
viewRef |
`RefObject | RefCallback` | A ref object or function to get a ref for the View that wraps the StickyHeaderView. |
import { StickyHeaderView } from "react-native-scrollables";
function DolphinAndSharkFacts({ dolphinFacts, sharkFacts }) {
return (
<View>
<StickyHeaderView stickyHeaderElement={<Text>Dolphin Facts</Text>}>
{dolphinFacts.map((fact) => (
<FactText key={fact.key} text={fact.text} />
))}
</StickyHeaderView>
<StickyHeaderView stickyHeaderElement={<Text>Shark Facts</Text>}>
{sharkFacts.map((fact) => (
<FactText key={fact.key} text={fact.text} />
))}
</StickyHeaderView>
</View>
);
}This component-and-object pair lets you scroll to specific points on a page. createAnchorHandle is a function that returns a special AnchorHandle object that you can pass to an AnchorView element. AnchorHandles have a scrollTo() method that you can then call to scroll to the AnchorView being passed that handle.
| Prop Name | Type | Description | Required |
|---|---|---|---|
anchorHandle |
AnchorHandle |
An object returned by a call to createAnchorHandle(). |
Yes |
function DinnerMenu({ menuItems }) {
const anchorHandles = menuItems.map(() => createAnchorHandle());
return (
<View>
<MenuLinks>
{menuItems.map((item, index) => (
<MenuItemLink
key={item.key}
item={item}
onPress={() => anchorHandles[index].scrollTo()}
/>
))}
</MenuLinks>
<MenuDescriptions>
{menuItems.map((item, index) => (
<AnchorView anchorHandle={anchorHandles[index]} key={item.key}>
<MenuItemDescription item={item} />
</AnchorView>
))}
</MenuDescriptions>
</View>
);
}Given a list of elements, LazyList renders only the elements that the user has reached by scrolling the nearest parent EnhancedScrollView (plus a couple elements below, so the user doesn't see a blank space). It starts by rendering as few elements as possible, then adding more and more elements as the user scrolls. It only ever adds elements as the user scrolls, and never hides or removes them.
LazyList is basically an alternative to React Native's built-in FlatList, except that it's not scrollable on its own. To achieve something similar to React Native's SectionList instead, you can put LazyLists inside other LazyLists (and even put those inside other LazyLists).
| Prop Name | Type | Description | Required |
|---|---|---|---|
elements |
ReactNode[] |
An array of React elements to render lazily. | Yes |
avgItemHeight |
number |
The expected height of a single element in the elements array, on average. It's highly recommended to pass this prop. It's used to calculate how many items the list should start with, and to pad the bottom of the list with empty space before everything's rendered. Feel free to pass in an educated guess! |
No |
itemsToMountWith |
number |
Manually sets the number of items the list should start out with. Pass this in if you really can't guess a value for avgItemHeight. Also, it's best not to pass this to LazyLists that end up containing other LazyLists (they'll default to starting with 1 item). |
No |
onFullyLoaded |
() => void |
A function that runs when the LazyList is finally rendering all its items. Useful for pagination. |
No |
import { LazyList } from "react-native-scrollables";
function ListOfFacts({ facts, loadMoreFacts }) {
return (
<LazyList
elements={facts.map((fact) => (
<FactText key={fact.key} text={fact.text} />
))}
avgItemHeight={30}
onFullyLoaded={loadMoreFacts}
/>
);
}The HideOffscreen component replaces its children with blank padding when the user scrolls far away from it. If you have a very long list of smaller items, it's recommended to wrap each of them in a HideOffscreen, so that they aren't all rendered at once. You'll generally use these in conjunction with LazyList.
| Prop Name | Type | Description | Required |
|---|---|---|---|
hideDistance |
number |
A custom distance, in style units, from the edges of the screen at which to hide the children of the HideOffscreen. |
No |
import { HideOffscreen, LazyList } from "react-native-scrollables";
function ReallyLongListOfFacts({ soManyFacts }) {
return (
<LazyList
elements={soManyFacts.map((fact) => (
<HideOffscreen>
<FactText key={fact.key} text={fact.text} />
</HideOffscreen>
))}
avgItemHeight={30}
/>
);
}The above components are built using these lower-level hooks. These come immensely in handy for interacting with scrollable views.
A React hook that returns a React ref object containing the internal ScrollView of the closest parent EnhancedScrollView.
() => RefObject<ScrollView>
import { useScrollViewRef } from "react-native-scrollables";
function ScrollToTopButton() {
const scrollViewRef = useScrollViewRef();
return (
<Button
title="Scroll to top"
onPress={() => {
if (scrollViewRef.current) {
scrollViewRef.current.scrollTo({ y: 0 });
}
}}
/>
);
}A React hook that returns natively driven Animated.Values tracking the x and y scroll of the closest parent EnhancedScrollView element.
() => ({ x: Animated.Value; y: Animated.Value })
import { useAnimatedScrollValue } from "react-native-scrollables";
function AnimatedHeaderImage({ source }) {
const scrollY = useAnimatedScrollValue().y;
return (
<Animated.Image
source={source}
style={{
width: "100%",
transform: [{ translateY: Animated.divide(scrollY, 2) }],
}}
/>
);
}A React hook that returns an object for reading and subscribing to the scroll events of the closest parent EnhancedScrollView. Use useAnimatedScrollValue() (not this) for animations!
() => {
latest: NativeScrollEvent | null;
subscribe(
listener: (scrollEvent: NativeScrollEvent) => void
): () => void
}(The subscribe method's type is a little confusing - click here for a breakdown)
- The
scrollEventsobject returned byuseScrollEventshas asubscribemethod, which you can use to subscribe to scroll events. - The
subscribemethod takes in another function that will run on every scroll event. - Calling the
subscribemethod returns an "unsubscribe" function, that will un-register the scroll event subscription that was created bysubscribe.
import { useAddScrollListener } from "react-native-scrollables";
function TermsAndConditionsButton({ termsAndConditionsHeight, onContinue }) {
const [hasScrolledEnough, setHasScrolledEnough] = useState(false);
const scrollEvents = useScrollEvents();
useEffect(() => {
const removeListener = scrollEvents.subscribe((event) => {
const scrollAmount = event.contentOffset.y;
if (scrollAmount > termsAndConditionsHeight) {
setHasScrolledEnough(true);
}
});
return () => {
removeListener();
};
}, [termsAndConditionsHeight, scrollEvents]);
return (
<Button
title="Accept the terms and conditions"
onPress={onContinue}
disabled={!hasScrolledEnough}
/>
);
}A React hook that returns a function for measuring the position of a host component (e.g. a View) relative to the nearest parent EnhancedScrollView. Since the measurements happen asynchronously in native code, the function returned by this hook is an async function.
useGetPositionInScrollView is mostly useful together with other react-native-scrollables hooks. For example, if you know the y-position of an Animated.View, you can use useAnimatedScrollValue to animate it as you scroll past it!
() => (viewRef: NativeMethods) => Promise<{ x: number; y: number; }>
The NativeMethods part looks weird, but what it means most often in practice is that you can pass in a ref value for a View, a Text, an Image, or a TextInput.
import { useGetPositionInScrollView } from "react-native-scrollables";
function LayoutIndicatorMessage() {
const getPositionInScrollView = useGetPositionInScrollView();
const textRef = useRef();
const [y, setY] = useState(0);
return (
<Text
ref={textRef}
onLayout={async () => {
if (!textRef.current) return;
const position = await getPositionInScrollView(textRef.current);
setY(position.y);
}}
>
The y-value of this text is {y}
</Text>
);
}