react / react/react-native

Horizontal ScrollView touchables stop responding when nested inside a sticky header (RN 0.79.2)

Ouverte
#51,290 20 commentaires 16 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

Component: ScrollView Needs: Triage :mag: Newer Patch Available
Langage dominant
C++
Étoiles
127k
Forks
25.3k
Merge moyen
1 j 23 h
PR mergées (30 j)
4

Description

Description

When you nest a horizontal ScrollView with touchable tabs inside a vertical ScrollView (or SectionList) that uses stickyHeaderIndices, the horizontal scroll still works, but the individual tab TouchableOpacity buttons no longer fire their onPress once the header becomes pinned. As soon as you scroll back up and the header unsticks, the buttons work again.


Steps to reproduce
  1. Create a fresh RN 0.79.2 project (or use your existing app).
  2. Copy this component into App.js (or similar):
import React, { useState } from 'react'
import { View, Text, ScrollView, TouchableOpacity, StyleSheet, SafeAreaView } from 'react-native'

const ItemBox = ({ title }) => (
  <View style={styles.itemBox}>
    <Text>{title}</Text>
  </View>
)

export default function NestedScrollTest() {
  const tabs = [
    { key: 'one', title: 'Tab One' },
    { key: 'two', title: 'Tab Two' },
    { key: 'three', title: 'Tab Three' },
    { key: 'four', title: 'Tab Four' },
    { key: 'five', title: 'Tab Five' },
  ]

  // Generate lots of items so it scrolls
  const allItems = Array.from({ length: 50 }, (_, i) => `Item #${i + 1}`)

  const [activeTab, setActiveTab] = useState(tabs[0].key)

  const filteredItems = allItems.filter(
    (_, idx) => idx % tabs.length === tabs.findIndex(t => t.key === activeTab)
  )

  return (
    <SafeAreaView style={styles.container}>
      <TouchableOpacity
        style={styles.submitButton}
        onPress={() => alert(`Submitting on "${activeTab}"`)}
      >
        <Text style={styles.submitText}>Top Submit Button</Text>
      </TouchableOpacity>
      {/* Vertical scroll area with sticky header */}
      <ScrollView
        style={styles.scrollContainer}
        contentContainerStyle={{ paddingBottom: 20 }}
        nestedScrollEnabled
        removeClippedSubviews={false}
        stickyHeaderIndices={[0]}
      >
        {/* Sticky horizontal tab bar */}
        <View style={styles.stickyContainer} pointerEvents="box-none">
          <ScrollView
            horizontal
            showsHorizontalScrollIndicator={false}
            nestedScrollEnabled
            pointerEvents="auto"
            contentContainerStyle={styles.tabBar}
            onStartShouldSetResponder={() => true}
          >
            {tabs.map(tab => (
              <TouchableOpacity
                key={tab.key}
                onPress={() => setActiveTab(tab.key)}
                activeOpacity={0.7}
                style={[
                  styles.tabButton,
                  activeTab === tab.key && styles.activeTab,
                ]}
              >
                <Text style={
                  activeTab === tab.key ? styles.activeTabText : styles.tabText
                }>
                  {tab.title}
                </Text>
              </TouchableOpacity>
            ))}
          </ScrollView>
        </View>

        {filteredItems.map(title => (
          <ItemBox key={title} title={title} />
        ))}
      </ScrollView>

      <TouchableOpacity
        style={styles.submitButton}
        onPress={() => alert(`Submitting on "${activeTab}"`)}
      >
        <Text style={styles.submitText}>Bottom Submit Button</Text>
      </TouchableOpacity>
    </SafeAreaView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  scrollContainer: {
    flex: 1,
  },
  stickyContainer: {
    backgroundColor: 'white',
    zIndex: 10,
    elevation: 4,
  },
  tabBar: {
    paddingVertical: 10,
    paddingHorizontal: 8,
  },
  tabButton: {
    paddingHorizontal: 16,
    paddingVertical: 8,
    marginRight: 8,
    borderRadius: 4,
    backgroundColor: '#EEE',
  },
  activeTab: {
    backgroundColor: '#4A90E2',
  },
  tabText: {
    fontSize: 14,
    color: '#333',
  },
  activeTabText: {
    fontSize: 14,
    color: '#FFF',
  },
  itemBox: {
    height: 80,
    marginHorizontal: 16,
    marginVertical: 8,
    backgroundColor: '#F9F9F9',
    borderRadius: 6,
    justifyContent: 'center',
    alignItems: 'center',
    elevation: 2,
  },
  submitButton: {
    padding: 16,
    backgroundColor: '#28A745',
    alignItems: 'center',
  },
  submitText: {
    color: '#FFF',
    fontSize: 16,
  },
})

  1. Run on Android.

  2. Scroll until the tab bar sticks to the top.

  3. Tap any tab button—nothing fires.

  4. Scroll up one pixel; tap again—the press now registers.

React Native Version

0.79.2

Affected Platforms

Runtime - Android

Output of npx @react-native-community/cli info
System:
  OS: Linux 6.8 Ubuntu 24.04.2 LTS 24.04.2 LTS (Noble Numbat)
  CPU: (6) x64 Intel(R) Core(TM) i5-9400F CPU @ 2.90GHz
  Memory: 12.45 GB / 23.38 GB
  Shell:
    version: 5.2.21
    path: /bin/bash
Binaries:
  Node:
    version: 22.11.0
    path: ~/.nvm/versions/node/v22.11.0/bin/node
  Yarn:
    version: 1.22.22
    path: ~/.nvm/versions/node/v22.11.0/bin/yarn
  npm:
    version: 10.9.0
    path: ~/.nvm/versions/node/v22.11.0/bin/npm

npmPackages:
  "@react-native-community/cli":
    installed: 18.0.0
    wanted: 18.0.0
  react:
    installed: 19.0.0
    wanted: 19.0.0
  react-native:
    installed: 0.79.2
    wanted: 0.79.2
MANDATORY Reproducer

https://github.com/Mootzali/NestedScrollIssue/

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez par le reproducer obligatoire et son composant App.js sur Android, en comparant les pressions sur les onglets lorsque l’en-tête est épinglé et lorsqu’il ne l’est pas. Suivez la gestion des interactions tactiles Android de React Native pour le ScrollView horizontal imbriqué et le sticky header, puis vérifiez que les contrôles TouchableOpacity des onglets répondent lorsqu’ils sont épinglés sans perturber le défilement.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
react-native
Domaine
mobile
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
Calme
Clarté
Plutôt claire
Accessibilité débutants
48/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.