Skip to content

API reference

SmartRefreshLayout

tsx
import { SmartRefreshLayout } from 'expo-smartrefreshlayout';

The component requires exactly one FlatList, SectionList, ScrollView, or compatible native scrolling child.

Props

PropTypeDefaultDescription
childrenReactElementrequiredThe single scrolling child
refreshHeaderReactElement-React content mounted in the Android/iOS native refresh-header slot; replaces the Classic or Material header selected by headerStyle
refreshEnabledbooleanhas onRefreshEnables pull to refresh
loadMoreEnabledbooleanhas onLoadMoreEnables load more
loadMoreMode'pull' | 'auto''pull'pull requires a pull-release; auto waits for overflowing content and a real upward scroll
autoLoadMoreEnabledbooleanfalseDeprecated compatibility alias for loadMoreMode="auto"; ignored when loadMoreMode is set
refreshingbooleanuncontrolledControlled refresh state
loadingMorebooleanuncontrolledControlled pagination state
hasMorebooleantrueShows no-more-data and prevents another load when false
hapticsEnabledbooleantrueHaptic feedback at the release threshold
headerStyle'classic' | 'material''classic'Refresh header implementation
primaryColorColorValueplatform defaultClassic header/footer primary background; Android Material also uses it as its primary color
indicatorColorColorValueplatform defaultIndicator color
titleColorColorValueplatform defaultClassic state-label color
classicSpinnerStyle'scale' | 'translate' | 'fixed-behind''translate'Classic header motion on Android and iOS
classicEnableLastTimebooleantrueShows the Classic last-refresh label on Android and iOS
materialShowBezierWavebooleanfalseAndroid-only Material bezier background
materialEnableHeaderTranslationContentbooleanfalseAndroid-only Material content translation
materialProgressBackgroundColorColorValueMaterial defaultMaterial progress-circle background on Android and iOS
messagesPartial<RefreshMessages>English defaultsState-label overrides
onRefresh(request) => void | Promise<void>-Refresh callback
onLoadMore(request) => void | LoadMoreResult | Promise<void | LoadMoreResult>-Pagination callback; may return the next hasMore value
onRefreshError(error: unknown) => void-Refresh failure notification
onLoadMoreError(error: unknown) => void-Pagination failure notification
onStateChange(state: RefreshState) => void-Native state change
onHeaderMoving(event: HeaderMovingEvent) => void-Custom-header pull-distance updates, including spring-back after release

Other ViewProps pass through to the native container.

Custom native header

refreshHeader is mounted in a real Android/iOS native refresh-header slot, rather than as an ordinary child of the list. When supplied, it replaces the Classic or Material header chosen by headerStyle. A custom header currently has a fixed logical height of 80; lay its content out inside that area. For display-only content, use pointerEvents="none" so it cannot intercept the list's pull gesture.

onHeaderMoving fires while dragging and while the header springs back after release. offset, height, and maxDragHeight are platform-independent logical pixels (Android dp / iOS pt); percent >= 1 means the refresh threshold has been reached.

Header colors and behavior

Classic headers and footers support primaryColor, indicatorColor, and titleColor on both platforms. The Classic header additionally supports classicSpinnerStyle and classicEnableLastTime. The Material header supports indicatorColor and materialProgressBackgroundColor on both platforms. Android additionally uses primaryColor for Material and exposes its bezier and content-translation switches. iOS safely ignores those two Android-only layout switches and keeps primaryColor for Classic headers and footers.

tsx
<SmartRefreshLayout
  headerStyle="classic"
  primaryColor="#1677ff"
  indicatorColor="#ffffff"
  titleColor="#ffffff"
  classicSpinnerStyle="fixed-behind"
  classicEnableLastTime
  onRefresh={reload}
>
  <FlatList {...listProps} />
</SmartRefreshLayout>
tsx
<SmartRefreshLayout
  headerStyle="material"
  primaryColor="#52c41a"
  indicatorColor="#ffffff"
  materialShowBezierWave
  materialEnableHeaderTranslationContent={false}
  materialProgressBackgroundColor="#52c41a"
  onRefresh={reload}
>
  <FlatList {...listProps} />
</SmartRefreshLayout>

Header configuration can change while the component is mounted. Android rebuilds its Classic header when the spinner mode changes; if a request is active, the rebuild waits until the native layout is idle.

Completion behavior

Without refreshing, a settled Promise from onRefresh automatically finishes the native refresh. Without loadingMore, onLoadMore follows the same rule. A synchronous callback therefore completes immediately.

Thrown errors finish the matching uncontrolled animation with failure and call onRefreshError or onLoadMoreError when provided. The component does not create an unhandled Promise rejection when an error handler is absent.

Passing refreshing or loadingMore makes that request controlled. The caller must restore the matching prop to false to finish the native animation.

Refresh and pagination share one lock per view instance. Each accepted request has a requestId; duplicate gestures, crossed refresh/load requests, and stale delayed finish commands are ignored.

In automatic mode, the footer does not request data on mount, for short content, or merely because it appears. The user must scroll upward after content exceeds the viewport. A completed automatic request needs another upward scroll before it can unlock again. Do not combine it with FlatList.onEndReached.

Callback and state types

ts
interface RefreshMessages {
  pullDown: string;
  releaseToRefresh: string;
  refreshing: string;
  refreshComplete: string;
  pullUp: string;
  releaseToLoadMore: string;
  loadingMore: string;
  noMoreData: string;
}

interface RefreshRequest {
  requestId: number;
  source: 'gesture' | 'programmatic';
}

interface LoadMoreResult {
  hasMore: boolean;
}

interface HeaderMovingEvent {
  /** Pull distance relative to the refresh trigger threshold. */
  percent: number;
  /** Current header pull distance in logical pixels (dp/pt). */
  offset: number;
  /** Native header height in logical pixels (dp/pt). */
  height: number;
  /** Maximum pull distance in logical pixels (dp/pt). */
  maxDragHeight: number;
  /** Whether the user is actively dragging the scroll view. */
  isDragging: boolean;
}

type RefreshState =
  | 'idle'
  | 'pulling'
  | 'ready'
  | 'refreshing'
  | 'loading'
  | 'no-more-data';

messages accepts a partial object; omitted labels retain the built-in English defaults. Returning { hasMore: false } from onLoadMore locks the footer in that same request, without waiting for a new render. Otherwise the latest hasMore prop is used.

SmartRefreshLayoutRef

tsx
import type { SmartRefreshLayoutRef } from 'expo-smartrefreshlayout';
ts
interface SmartRefreshLayoutRef {
  beginRefresh(delay?: number): boolean;
  finishRefresh(options?: { success?: boolean; delay?: number }): void;
  beginLoadMore(delay?: number): boolean;
  finishLoadMore(options?: {
    success?: boolean;
    hasMore?: boolean;
    delay?: number;
  }): void;
  resetNoMoreData(): void;
}

beginRefresh and beginLoadMore return true when the mounted instance accepted the command. They return false for an active request; beginLoadMore also returns false when hasMore is false. A non-finite or negative delay is normalized to 0 milliseconds.

finishRefresh and finishLoadMore complete only the matching active operation. Their defaults are success: true and delay: 0; finishLoadMore({ hasMore: false }) puts the native footer into no-more-data. resetNoMoreData() clears that native footer state. In a controlled pagination flow, also restore hasMore to true, otherwise the next render applies no-more-data again.

Compatibility export

v2 temporarily keeps the old component name as an alias:

ts
import { ExpoSmartrefreshlayoutView } from 'expo-smartrefreshlayout';

It accepts v2 props. The old ExpoSmartrefreshlayoutModule and legacy v1 props are not present.

SmartSecondFloorLayout (Android only)

tsx
import { SmartSecondFloorLayout } from 'expo-smartrefreshlayout';

This component wraps SmartRefreshLayout's Android TwoLevelHeader. children is the ordinary page's single scrolling child; secondFloor is the full-screen formal content. secondFloorBackground is an optional layer behind it that appears during the reveal while formal content fades in. There is no footer or onLoadMore API.

Mounting this component on iOS throws an explicit error because there is no equivalent native interaction. Branch on the platform before rendering it.

Props

PropTypeDefaultDescription
childrenReactElementrequiredNormal page's single scrolling child
secondFloorReactElementrequiredFull-screen second-floor content, such as a ScrollView or FlatList
secondFloorBackgroundReactElement-Optional backdrop behind formal content
refreshEnabledbooleanhas onRefreshEnables ordinary pull to refresh
refreshingbooleanuncontrolledControlled ordinary refresh state
hapticsEnabledbooleantrueFeedback at refresh or second-floor release thresholds
secondFloorEnabledbooleantrueEnables the second-floor gesture and open command
headerInsetnumber0Logical height reserved for an overlay toolbar
maxRatenumber2.5Maximum pull multiplier, normalized to 1.2..5
floorRatenumber1.9Second-floor release multiplier, at least 1.1 and below maxRate
refreshRatenumber1Ordinary refresh multiplier, at least 0.25 and below floorRate
floorDurationnumber1000Second-floor open/stay duration in milliseconds, normalized to 0..10000
pullToCloseEnabledbooleantrueEnables the downward close gesture
bottomPullUpToCloseRatenumber1/6Bottom upward-close rate, normalized to 0.01..0.5
primaryColorColorValueplatform defaultClassic header primary background
indicatorColorColorValueplatform defaultClassic indicator color
titleColorColorValueplatform defaultClassic state-label color
classicEnableLastTimebooleantrueShows the Classic last-refresh label
messagesPartial<SecondFloorMessages>English defaultsOrdinary-refresh label overrides
onRefresh(request) => void | Promise<void>-Ordinary refresh callback
onRefreshError(error: unknown) => void-Refresh failure notification
onStateChange(state: SecondFloorState) => void-Refresh and second-floor lifecycle state
onSecondFloorOpen() => void-Open animation completed
onSecondFloorClose() => void-Close animation completed

Conflicting refreshRate, floorRate, and maxRate values are normalized in JavaScript and Android to preserve their threshold ordering rather than throwing. Pass headerInset when a toolbar overlays the page: it becomes part of the native header height, so the visible header and pull thresholds agree.

ts
interface SecondFloorMessages {
  pullDown: string;
  releaseToRefresh: string;
  refreshing: string;
  refreshComplete: string;
}

type SecondFloorState =
  | 'idle'
  | 'pulling'
  | 'ready'
  | 'refreshing'
  | 'release-to-second-floor'
  | 'second-floor-opening'
  | 'second-floor'
  | 'second-floor-closing';

SmartSecondFloorLayoutRef

ts
interface SmartSecondFloorLayoutRef {
  beginRefresh(delay?: number): boolean;
  finishRefresh(options?: { success?: boolean; delay?: number }): void;
  openSecondFloor(): boolean;
  closeSecondFloor(): boolean;
}

beginRefresh returns false while a refresh or second-floor transition is active. openSecondFloor returns true once an idle, mounted native instance accepted the command, and closeSecondFloor returns true when an opening or open floor accepted its close command. These boolean values describe command acceptance, not animation completion; observe onSecondFloorOpen, onSecondFloorClose, or onStateChange for lifecycle completion.

Second-floor content can scroll, but the outer TwoLevelHeader takes over boundary drags. Enable nestedScrollEnabled on an inner ScrollView or FlatList and avoid competing horizontal or edge gestures at the same boundary.

Released under the MIT License.