Skip to content

Commit

Permalink
feat(boilerplate) @shopify/flash-list integration and ListView HOC (#…
Browse files Browse the repository at this point in the history
…2513)

* feat(boilerplate) @shopify/flash-list integration

This integrates @shopify/flash-list in all places where we used to have a FlatList and updates the expo to compatibility object to use the correct expected version while using the latest for expo prebuild and bare workflows.

* fix: ios compiling issue when the user specifies the new architecture

* fix: Get FlashList working in new architecture using interop layer

iOS seems to have problems with flashlist using interop, but android seems to work just fine.

* feat: adds ListView Higher Order Component

Displays a FlashList or FlatList depending on language RTL features. Needs a little more work on the `ref` passing.

* chore: formatting

* refactor(boilerplate): ListView HOC types

* chore(tests) Tests passing after adding new ListView component

* ci: bun version lock

---------

Co-authored-by: Frank Calise <fcalise@gmail.com>
  • Loading branch information
markrickert and frankcalise authored Oct 11, 2023
1 parent bddaea5 commit 1ac2870
Show file tree
Hide file tree
Showing 15 changed files with 104 additions and 29 deletions.
3 changes: 2 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ jobs:
resource_class: large
steps:
- checkout
- bun-orb/setup
- bun-orb/setup:
version: 1.0.3
- restore_cache:
name: Restore node modules
keys:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Nothing makes it into Ignite unless it's been proven on projects that Infinite R
| Jest | Test Runner | v26 | Standard test runner for JS apps |
| Maestro | Testing Framework | | Automate end-to-end UI testing |
| date-fns | Date library | v2 | Excellent date library |
| FlashList | FlatList replacement | v1 | A performant drop-in replacement for FlatList |

Ignite also comes with a [component library](https://github.com/infinitered/ignite/blob/master/docs/Components.md) that is tuned for custom designs, theming support, testing, custom fonts, generators, and much, much more.

Expand Down
44 changes: 44 additions & 0 deletions boilerplate/app/components/ListView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { forwardRef, PropsWithoutRef } from "react"
import { FlatList } from "react-native"
import { isRTL } from "app/i18n"
import { FlashList, FlashListProps } from "@shopify/flash-list"

export type ListViewRef<T> = FlashList<T> | FlatList<T>

export type ListViewProps<T> = PropsWithoutRef<FlashListProps<T>>

/**
* This is a Higher Order Component meant to ease the pain of using @shopify/flash-list
* when there is a chance that a user would have their device language set to an
* RTL language like Arabic or Punjabi. This component will use react-native's
* FlatList if the user's language is RTL or FlashList if the user's language is LTR.
*
* Because FlashList's props are a superset of FlatList's, you must pass estimatedItemSize
* to this component if you want to use it.
*
* This is a temporary workaround until the FlashList component supports RTL at
* which point this component can be removed and we will default to using FlashList everywhere.
*
* @see {@link https://github.com/Shopify/flash-list/issues/544|RTL Bug Android}
* @see {@link https://github.com/Shopify/flash-list/issues/840|Flashlist Not Support RTL}
*
* @param props - FlashListProps | FlatListProps
* @param forwardRef - React.Ref<ListProps<T>>
* @returns JSX.Element
*/

const ListViewComponent = forwardRef(
<T,>(props: ListViewProps<T>, ref: React.ForwardedRef<ListViewRef<T>>) => {
const ListComponentWrapper = isRTL ? FlatList : FlashList

return <ListComponentWrapper {...props} ref={ref} />
},
)

ListViewComponent.displayName = "ListView"

export const ListView = ListViewComponent as <T>(
props: ListViewProps<T> & {
ref?: React.RefObject<ListViewRef<T>>
},
) => React.ReactElement
1 change: 1 addition & 0 deletions boilerplate/app/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from "./Card"
export * from "./Header"
export * from "./Icon"
export * from "./ListItem"
export * from "./ListView"
export * from "./Screen"
export * from "./Text"
export * from "./TextField"
Expand Down
16 changes: 7 additions & 9 deletions boilerplate/app/screens/DemoPodcastListScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
// Interested in migrating from FlatList to FlashList? Check out the recipe in our Ignite Cookbook
// https://ignitecookbook.com/docs/recipes/MigratingToFlashList
import { observer } from "mobx-react-lite"
import React, { FC, useEffect, useMemo } from "react"
import {
AccessibilityProps,
ActivityIndicator,
FlatList,
Image,
ImageStyle,
Platform,
Expand All @@ -14,14 +11,15 @@ import {
View,
ViewStyle,
} from "react-native"
import { type ContentStyle } from "@shopify/flash-list"
import Animated, {
Extrapolate,
interpolate,
useAnimatedStyle,
useSharedValue,
withSpring,
} from "react-native-reanimated"
import { Button, Card, EmptyState, Icon, Screen, Text, Toggle } from "../components"
import { Button, Card, EmptyState, Icon, ListView, Screen, Text, Toggle } from "../components"
import { isRTL, translate } from "../i18n"
import { useStores } from "../models"
import { Episode } from "../models/Episode"
Expand Down Expand Up @@ -66,11 +64,12 @@ export const DemoPodcastListScreen: FC<DemoTabScreenProps<"DemoPodcastList">> =
safeAreaEdges={["top"]}
contentContainerStyle={$screenContentContainer}
>
<FlatList<Episode>
data={episodeStore.episodesForList}
<ListView<Episode>
contentContainerStyle={$listContentContainer}
data={episodeStore.episodesForList.slice()}
extraData={episodeStore.favorites.length + episodeStore.episodes.length}
contentContainerStyle={$flatListContentContainer}
refreshing={refreshing}
estimatedItemSize={177}
onRefresh={manualRefresh}
ListEmptyComponent={
isLoading ? (
Expand Down Expand Up @@ -118,7 +117,6 @@ export const DemoPodcastListScreen: FC<DemoTabScreenProps<"DemoPodcastList">> =
}
renderItem={({ item }) => (
<EpisodeCard
key={item.guid}
episode={item}
isFavorite={episodeStore.hasFavorite(item)}
onPressFavorite={() => episodeStore.toggleFavorite(item)}
Expand Down Expand Up @@ -296,7 +294,7 @@ const $screenContentContainer: ViewStyle = {
flex: 1,
}

const $flatListContentContainer: ViewStyle = {
const $listContentContainer: ContentStyle = {
paddingHorizontal: spacing.lg,
paddingTop: spacing.lg + spacing.xl,
paddingBottom: spacing.lg,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Link, RouteProp, useRoute } from "@react-navigation/native"
import React, { FC, ReactElement, useEffect, useRef, useState } from "react"
import {
Dimensions,
FlatList,
Image,
ImageStyle,
Platform,
Expand All @@ -11,9 +10,10 @@ import {
View,
ViewStyle,
} from "react-native"
import { type ContentStyle } from "@shopify/flash-list"
import { DrawerLayout, DrawerState } from "react-native-gesture-handler"
import { useSharedValue, withTiming } from "react-native-reanimated"
import { ListItem, Screen, Text } from "../../components"
import { ListItem, ListView, ListViewRef, Screen, Text } from "../../components"
import { isRTL } from "../../i18n"
import { DemoTabParamList, DemoTabScreenProps } from "../../navigators/DemoNavigator"
import { colors, spacing } from "../../theme"
Expand Down Expand Up @@ -88,7 +88,7 @@ export const DemoShowroomScreen: FC<DemoTabScreenProps<"DemoShowroom">> =
const timeout = useRef<ReturnType<typeof setTimeout>>()
const drawerRef = useRef<DrawerLayout>(null)
const listRef = useRef<SectionList>(null)
const menuRef = useRef<FlatList>(null)
const menuRef = useRef<ListViewRef<DemoListItem["item"]>>(null)
const progress = useSharedValue(0)
const route = useRoute<RouteProp<DemoTabParamList, "DemoShowroom">>()
const params = route.params
Expand Down Expand Up @@ -181,12 +181,13 @@ export const DemoShowroomScreen: FC<DemoTabScreenProps<"DemoShowroom">> =
<Image source={logo} style={$logoImage} />
</View>

<FlatList<{ name: string; useCases: string[] }>
<ListView<DemoListItem["item"]>
ref={menuRef}
contentContainerStyle={$flatListContentContainer}
contentContainerStyle={$listContentContainer}
estimatedItemSize={250}
data={Object.values(Demos).map((d) => ({
name: d.name,
useCases: d.data.map((u) => u.props.name),
useCases: d.data.map((u) => u.props.name as string),
}))}
keyExtractor={(item) => item.name}
renderItem={({ item, index: sectionIndex }) => (
Expand Down Expand Up @@ -237,7 +238,7 @@ const $drawer: ViewStyle = {
flex: 1,
}

const $flatListContentContainer: ViewStyle = {
const $listContentContainer: ContentStyle = {
paddingHorizontal: spacing.lg,
}

Expand Down
19 changes: 8 additions & 11 deletions boilerplate/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
/* eslint-disable react/jsx-key, react-native/no-inline-styles */
import React from "react"
import { TextStyle, View, ViewStyle } from "react-native"
import { FlatList } from "react-native-gesture-handler"
import { Icon, ListItem, Text } from "../../../components"
import { Icon, ListItem, ListView, Text } from "../../../components"
import { colors, spacing } from "../../../theme"
import { Demo } from "../DemoShowroomScreen"
import { DemoDivider } from "../DemoDivider"
import { DemoUseCase } from "../DemoUseCase"

const flatListData =
const listData =
`Tempor Id Ea Aliqua Pariatur Aliquip. Irure Minim Voluptate Consectetur Consequat Sint Esse Proident Irure. Nostrud Elit Veniam Nostrud Excepteur Minim Deserunt Quis Dolore Velit Nulla Irure Voluptate Tempor. Occaecat Amet Laboris Nostrud Qui Do Quis Lorem Ex Elit Fugiat Deserunt. In Pariatur Excepteur Exercitation Ex Incididunt Qui Mollit Dolor Sit Non. Culpa Officia Minim Cillum Exercitation Voluptate Proident Laboris Et Est Reprehenderit Quis Pariatur Nisi`
.split(".")
.map((item) => item.trim())
Expand Down Expand Up @@ -36,11 +35,10 @@ const $customContainerStyle: ViewStyle = {
borderTopColor: colors.palette.neutral100,
}

const $flatListStyle: ViewStyle = {
const $listStyle: ViewStyle = {
height: 148,
paddingHorizontal: spacing.xs,
backgroundColor: colors.palette.neutral200,
flex: 1,
overflow: "scroll",
}

export const DemoListItem: Demo = {
Expand Down Expand Up @@ -145,13 +143,12 @@ export const DemoListItem: Demo = {
</DemoUseCase>,

<DemoUseCase
name="Integrating w/ FlatList"
name="Integrating w/ FlatList & FlashList"
description="The component can be easily integrated with your favorite list interface."
>
<View style={{ height: 148 }}>
<FlatList<string>
data={flatListData}
style={$flatListStyle}
<View style={$listStyle}>
<ListView<string>
data={listData}
renderItem={({ item, index }) => (
<ListItem
text={item}
Expand Down
1 change: 1 addition & 0 deletions boilerplate/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@react-navigation/bottom-tabs": "^6.3.2",
"@react-navigation/native": "^6.0.2",
"@react-navigation/native-stack": "^6.0.2",
"@shopify/flash-list": "^1.6.1",
"apisauce": "3.0.1",
"date-fns": "^2.30.0",
"expo": "^49.0.13",
Expand Down
14 changes: 14 additions & 0 deletions boilerplate/react-native.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,18 @@ module.exports = {
// packages, you can add them to the referenced path below and uncomment this line.
// "./assets/fonts/"
],

// This prevents FlashList from rendering a large red box.
// See: https://github.com/reactwg/react-native-new-architecture/discussions/135
// Remove when FlashList fully supports the new architecture.
// https://github.com/Shopify/flash-list/pull/550
//
project: {
android: {
unstable_reactLegacyComponentNames: ["CellContainer", "AutoLayoutView"],
},
ios: {
unstable_reactLegacyComponentNames: ["CellContainer", "AutoLayoutView"],
},
},
}
13 changes: 13 additions & 0 deletions docs/Components-ListView.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# ListView Component

The `ListView` component is a Higher Order Component that uses [Shopify's FlashList](https://shopify.github.io/flash-list/) component to display a list unless the user's preferred language is RTL (right-to-left). This is because FlashList has [known](https://github.com/Shopify/flash-list/issues/544) [issues](https://github.com/Shopify/flash-list/issues/840) with RTL support. Once these issues with FlashList are resolved, this component will be deprecated and FlashList will be used directly.

## Props

The `ListView` component uses props from `FlashList` because they are a superset of `FlatList` and only require one extra prop to work for both component types.

> [Please see the FlashList documentation for more information on the props that are available.](https://shopify.github.io/flash-list/docs/usage)
### `estimatedItemSize`

> [Please see the FlashList documentation for more information on this prop.](https://shopify.github.io/flash-list/docs/estimated-item-size)
1 change: 1 addition & 0 deletions docs/Folder-Structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ This is where your components will live, the reusable building blocks to create
- [Header](./Components-Header.md)
- [Icon](./Components-Icon.md)
- [ListItem](./Components-ListItem.md)
- [ListView](./Components-ListView.md)
- [Screen](./Components-Screen.md)
- [Text](./Components-Text.md)
- [TextField](./Components-TextField.md)
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Check out this list of topics:
- [Header](./Components-Header.md)
- [Icon](./Components-Icon.md)
- [ListItem](./Components-ListItem.md)
- [ListView](./Components-ListView.md)
- [Screen](./Components-Screen.md)
- [Text](./Components-Text.md)
- [TextField](./Components-TextField.md)
Expand Down
1 change: 0 additions & 1 deletion src/commands/new.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,6 @@ module.exports = {
startSpinner(" Enabling New Architecture")
try {
const appJsonRaw = read("app.json")

const appJson = JSON.parse(appJsonRaw)
appJson.expo.plugins[1][1].ios.newArchEnabled = true
appJson.expo.plugins[1][1].android.newArchEnabled = true
Expand Down
1 change: 1 addition & 0 deletions src/tools/expoGoCompatibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const expoGoCompatExpectedVersions = {
"expo-file-system": "~15.4.4",
"expo-font": "~11.4.0",
"expo-localization": "~14.3.0",
"@shopify/flash-list": "1.4.3",
}

// This function takes a package.json file as a string and updates the versions of the
Expand Down
2 changes: 2 additions & 0 deletions test/vanilla/ignite-generate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ describe("ignite-cli generate", () => {
export * from \\"./Header\\"
export * from \\"./Icon\\"
export * from \\"./ListItem\\"
export * from \\"./ListView\\"
export * from \\"./Screen\\"
export * from \\"./Text\\"
export * from \\"./TextField\\"
Expand Down Expand Up @@ -342,6 +343,7 @@ describe("ignite-cli generate", () => {
export * from \\"./Header\\"
export * from \\"./Icon\\"
export * from \\"./ListItem\\"
export * from \\"./ListView\\"
export * from \\"./Screen\\"
export * from \\"./Text\\"
export * from \\"./TextField\\"
Expand Down

0 comments on commit 1ac2870

Please sign in to comment.