Skip to content

rn-scrollview-dynamic-padding: stable header spacing reported as changing padding #1785

Description

@thomasorgeval

A header-presence conditional selects layout spacing. The only caller always supplies the header, including when its tab changes, so paddingTop stays at 0. The rule nevertheless asserts that changing padding makes rows jump.

Reproduction

Reproduced with React Doctor 0.9.13, Node 24.18.0, on macOS. JSON report: schemaVersion: 3, ok: true, project complete: true. This is a static-analysis reproduction; no runtime jank is inferred from the warning.

Create a directory with this package.json, add the file below, and run:

npx --yes react-doctor@0.9.13 . --scope full --json --blocking none --yes --no-score
{"name":"react-doctor-rule-repros","private":true,"version":"1.0.0","dependencies":{"react":"19.2.7","react-native":"0.86.2","@shopify/flash-list":"2.3.2","@tanstack/react-query":"5.101.0","zustand":"5.0.14"}}

HeaderGrid.tsx:

import type { ReactElement } from 'react';
import { FlashList } from '@shopify/flash-list';
import { Text } from 'react-native';

const rows = [{ id: 'one' }];
function renderRow({ item }: { item: { id: string } }) {
  return <Text>{item.id}</Text>;
}

function Grid({ ListHeaderComponent }: { ListHeaderComponent?: ReactElement }) {
  return (
    <FlashList
      data={rows}
      keyExtractor={(item) => item.id}
      renderItem={renderRow}
      ListHeaderComponent={ListHeaderComponent}
      contentContainerStyle={{ paddingTop: ListHeaderComponent ? 0 : 16 }}
    />
  );
}

export function Screen({ selectedTab }: { selectedTab: 'profile' | 'badges' }) {
  // The header is present for both tabs: paddingTop is always zero.
  return <Grid ListHeaderComponent={<Text>{selectedTab}</Text>} />;
}

Actual

“Your users see rows jump when a changing paddingTop on contentContainerStyle shifts the whole list.”

The recommendation is contentInset={{ bottom: dynamicValue }}. That changes the edge from top to bottom and does not preserve this cross-platform content-spacing behavior.

Expected

Treat an unresolved expression as a potential cost, not proof that the value changes or causes jank. Either recognize statically provable stable cases or give conditional profiling guidance. The recommendation should preserve the affected edge and explain that contentInset is not a general cross-platform substitute for content padding.

Notes

This is not a request to exempt every prop-based expression: keyboard height and genuinely changing insets still deserve investigation. The reproduction demonstrates why the categorical message is too strong. React Native ScrollView contentInset.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions