iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

ScrollView

ScrollView renders all children at once and lets the user scroll through them. Use it for SHORT content (forms, settings); for long lists, switch to FlatList.

Scrolling, keyboards, sticky headers

EXAMPLE
import { useRef, useState } from 'react';
import {
    ScrollView,
    View,
    Text,
    StyleSheet,
    KeyboardAvoidingView,
    Platform,
    RefreshControl,
    SafeAreaView,
    StatusBar,
} from 'react-native';

// 1) Basic vertical scroll
function Settings() {
    return (
        <ScrollView contentContainerStyle={{ padding: 16 }}>
            <Section title="Profile">…</Section>
            <Section title="Notifications">…</Section>
            <Section title="Privacy">…</Section>
            <Section title="Account">…</Section>
        </ScrollView>
    );
}

// 2) Horizontal scroll (stories, image carousel)
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
    {items.map(item => (
        <Card key={item.id} item={item} />
    ))}
</ScrollView>

// 3) Paging (snap-to-page) — full-screen swipe
<ScrollView
    horizontal
    pagingEnabled
    showsHorizontalScrollIndicator={false}
    decelerationRate="fast"
>
    {pages.map(page => (
        <View key={page.id} style={{ width: SCREEN_W }}>
            …
        </View>
    ))}
</ScrollView>

// 4) Sticky headers — stay visible while scrolling
<ScrollView stickyHeaderIndices={[0]}>
    <View style={styles.sticky}>
        <Text style={styles.stickyText}>Top header</Text>
    </View>
    {sections.map(s => <SectionView key={s.id} section={s} />)}
</ScrollView>

// 5) Pull-to-refresh
function Feed() {
    const [refreshing, setRefreshing] = useState(false);
    async function onRefresh() {
        setRefreshing(true);
        await refetch();
        setRefreshing(false);
    }
    return (
        <ScrollView
            refreshControl={
                <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
            }
        >
            {items.map(item => <Item key={item.id} item={item} />)}
        </ScrollView>
    );
}

// 6) Detect scroll position
function WithHeaderShadow() {
    const [shadow, setShadow] = useState(false);
    return (
        <View>
            <View style={[styles.header, shadow && styles.headerShadow]}>
                <Text>App</Text>
            </View>
            <ScrollView
                onScroll={e => setShadow(e.nativeEvent.contentOffset.y > 4)}
                scrollEventThrottle={16}
            >
                {/* … */}
            </ScrollView>
        </View>
    );
}

// 7) Keyboard handling — KeyboardAvoidingView wrap
function Form() {
    return (
        <KeyboardAvoidingView
            style={{ flex: 1 }}
            behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
        >
            <ScrollView
                keyboardShouldPersistTaps="handled"
                contentContainerStyle={{ padding: 16 }}
            >
                <TextField label="Name" />
                <TextField label="Email" />
                <TextField label="Comment" multiline />
                <Button title="Submit" onPress={submit} />
            </ScrollView>
        </KeyboardAvoidingView>
    );
}

// keyboardShouldPersistTaps:
//   'always'   — taps pass through; keyboard never dismisses on tap
//   'handled'  — taps dismiss keyboard unless the child has its own handler
//   'never'    — taps always dismiss the keyboard (default)

// 8) Scroll programmatically
const ref = useRef(null);

function scrollToTop()    { ref.current.scrollTo({ y: 0, animated: true }); }
function scrollToBottom() { ref.current.scrollToEnd({ animated: true }); }
function scrollToY(y)      { ref.current.scrollTo({ y, animated: true }); }

<ScrollView ref={ref}>…</ScrollView>

// 9) Center vertically (small content, big screen)
<ScrollView contentContainerStyle={{ flexGrow: 1, justifyContent: 'center', padding: 16 }}>
    {/* … */}
</ScrollView>

// 10) Scrolling images / video — bounces feel right
<ScrollView
    bounces={true}                     // iOS bounce effect
    overScrollMode="never"             // Android — disable glow
    showsVerticalScrollIndicator={false}
>
    {/* … */}
</ScrollView>

// 11) Combine with SafeAreaView (notch + home indicator)
<SafeAreaView style={{ flex: 1 }}>
    <ScrollView>
        {/* … */}
    </ScrollView>
</SafeAreaView>

// 12) Common bugs
//   • Wrapping a FlatList inside a ScrollView — virtualisation breaks, performance dies
//   • Forgetting contentContainerStyle for padding (style is the OUTER, contentContainerStyle is the INNER)
//   • Long lists in ScrollView → mount EVERY child, slow first paint + memory
//   • Form inputs hidden by keyboard → wrap with KeyboardAvoidingView
//   • Sticky headers not working → check stickyHeaderIndices is correct
//   • Horizontal ScrollView with width-less children → no scrolling (give children explicit width)

// 13) Style vs contentContainerStyle
<ScrollView
    style={{ flex: 1, backgroundColor: '#f8fafc' }}        // OUTER container
    contentContainerStyle={{ padding: 16, gap: 12 }}        // INNER content area
>
    {/* … */}
</ScrollView>

// 14) Animated header — collapse on scroll
import Animated, { useAnimatedScrollHandler, useAnimatedStyle, useSharedValue, interpolate } from 'react-native-reanimated';

function CollapsingHeader() {
    const scrollY = useSharedValue(0);
    const onScroll = useAnimatedScrollHandler(e => { scrollY.value = e.contentOffset.y; });

    const headerStyle = useAnimatedStyle(() => ({
        height: interpolate(scrollY.value, [0, 100], [120, 56], 'clamp'),
        opacity: interpolate(scrollY.value, [0, 100], [1, 0.7], 'clamp'),
    }));

    return (
        <View style={{ flex: 1 }}>
            <Animated.View style={[styles.header, headerStyle]}>
                <Text style={styles.headerText}>App</Text>
            </Animated.View>
            <Animated.ScrollView onScroll={onScroll} scrollEventThrottle={16}>
                {/* … */}
            </Animated.ScrollView>
        </View>
    );
}

// 15) When to use what
// ScrollView  : 1-30 children, short content, settings, forms, single-screen scrolling
// FlatList    : 20+ items, lists from data arrays — virtualised
// SectionList : grouped data
// FlashList   : @shopify/flash-list, fastest virtualised list
// CustomScrollView with SliverList : Flutter equivalent

const styles = StyleSheet.create({
    header:       { height: 56, paddingHorizontal: 16, justifyContent: 'center', backgroundColor: 'white' },
    headerShadow: { shadowOpacity: 0.1, shadowOffset: { width: 0, height: 2 }, shadowRadius: 4, elevation: 2 },
    sticky:       { padding: 12, backgroundColor: '#0ea5e9' },
    stickyText:   { color: 'white', fontWeight: '600' },
});

Why it matters

Use ScrollView for short, fixed content (forms, settings). For long lists, switch to FlatList — it virtualises off-screen rows. Wrapping a FlatList inside a ScrollView breaks virtualisation entirely.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import { ScrollView, Text } from 'react-native';
<ScrollView contentContainerStyle={{ padding: 16 }}>
    {items.map(i => <Text key={i.id}>{i.label}</Text>)}
</ScrollView>
Try it Yourself »

Discussion

Loading…