From 55d8b8a2fc241ed03965827e0b741e1a55ca23f6 Mon Sep 17 00:00:00 2001 From: Mark Rickert Date: Thu, 5 Oct 2023 09:59:09 -0600 Subject: [PATCH 1/8] 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. --- README.md | 1 + .../app/screens/DemoPodcastListScreen.tsx | 14 ++++++-------- .../DemoShowroomScreen/DemoShowroomScreen.tsx | 11 ++++++----- .../DemoShowroomScreen/demos/DemoListItem.tsx | 18 ++++++++---------- boilerplate/package.json | 1 + src/tools/expoGoCompatibility.ts | 1 + 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 6cfeb6173..e9db00997 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/boilerplate/app/screens/DemoPodcastListScreen.tsx b/boilerplate/app/screens/DemoPodcastListScreen.tsx index e9a4c5c90..0f0d57606 100644 --- a/boilerplate/app/screens/DemoPodcastListScreen.tsx +++ b/boilerplate/app/screens/DemoPodcastListScreen.tsx @@ -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, @@ -14,6 +11,7 @@ import { View, ViewStyle, } from "react-native" +import { FlashList, type ContentStyle } from "@shopify/flash-list" import Animated, { Extrapolate, interpolate, @@ -66,11 +64,12 @@ export const DemoPodcastListScreen: FC> = safeAreaEdges={["top"]} contentContainerStyle={$screenContentContainer} > - - data={episodeStore.episodesForList} + + contentContainerStyle={$listContentContainer} + data={episodeStore.episodesForList.slice()} extraData={episodeStore.favorites.length + episodeStore.episodes.length} - contentContainerStyle={$flatListContentContainer} refreshing={refreshing} + estimatedItemSize={177} onRefresh={manualRefresh} ListEmptyComponent={ isLoading ? ( @@ -118,7 +117,6 @@ export const DemoPodcastListScreen: FC> = } renderItem={({ item }) => ( episodeStore.toggleFavorite(item)} @@ -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, diff --git a/boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx b/boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx index b0f133c63..535edf7fd 100644 --- a/boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx +++ b/boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx @@ -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, @@ -11,6 +10,7 @@ import { View, ViewStyle, } from "react-native" +import { FlashList, 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" @@ -88,7 +88,7 @@ export const DemoShowroomScreen: FC> = const timeout = useRef>() const drawerRef = useRef(null) const listRef = useRef(null) - const menuRef = useRef(null) + const menuRef = useRef>(null) const progress = useSharedValue(0) const route = useRoute>() const params = route.params @@ -181,9 +181,10 @@ export const DemoShowroomScreen: FC> = - + 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), @@ -237,7 +238,7 @@ const $drawer: ViewStyle = { flex: 1, } -const $flatListContentContainer: ViewStyle = { +const $listContentContainer: ContentStyle = { paddingHorizontal: spacing.lg, } diff --git a/boilerplate/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx b/boilerplate/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx index 615b29aba..51f21a048 100644 --- a/boilerplate/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx +++ b/boilerplate/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx @@ -1,14 +1,14 @@ /* 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 { FlashList } from "@shopify/flash-list" import { Icon, ListItem, 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()) @@ -36,11 +36,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 = { @@ -145,13 +144,12 @@ export const DemoListItem: Demo = { , - - - data={flatListData} - style={$flatListStyle} + + + data={listData} renderItem={({ item, index }) => ( Date: Thu, 5 Oct 2023 16:50:18 -0600 Subject: [PATCH 2/8] fix: ios compiling issue when the user specifies the new architecture --- src/commands/new.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands/new.ts b/src/commands/new.ts index bb2497629..37d2ea8c6 100644 --- a/src/commands/new.ts +++ b/src/commands/new.ts @@ -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 From 2630214ed7e7600259b67150b3ea58b0e06de579 Mon Sep 17 00:00:00 2001 From: Mark Rickert Date: Thu, 5 Oct 2023 16:51:05 -0600 Subject: [PATCH 3/8] 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. --- boilerplate/react-native.config.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/boilerplate/react-native.config.js b/boilerplate/react-native.config.js index cb15a3f6c..1821360fc 100644 --- a/boilerplate/react-native.config.js +++ b/boilerplate/react-native.config.js @@ -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"], + }, + }, } From 1e85d889248c01b8a599dd730897cfa606f144d1 Mon Sep 17 00:00:00 2001 From: Mark Rickert Date: Tue, 10 Oct 2023 17:37:21 -0600 Subject: [PATCH 4/8] 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. --- boilerplate/app/components/ListView.tsx | 33 +++++++++++++++++++ boilerplate/app/components/index.ts | 1 + .../app/screens/DemoPodcastListScreen.tsx | 6 ++-- .../DemoShowroomScreen/DemoShowroomScreen.tsx | 8 ++--- .../DemoShowroomScreen/demos/DemoListItem.tsx | 5 ++- docs/Components-ListView.md | 13 ++++++++ docs/Folder-Structure.md | 1 + docs/README.md | 1 + 8 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 boilerplate/app/components/ListView.tsx create mode 100644 docs/Components-ListView.md diff --git a/boilerplate/app/components/ListView.tsx b/boilerplate/app/components/ListView.tsx new file mode 100644 index 000000000..1821ee801 --- /dev/null +++ b/boilerplate/app/components/ListView.tsx @@ -0,0 +1,33 @@ +import React, { forwardRef, PropsWithoutRef } from "react" +import { FlatList } from "react-native" +import { isRTL } from "app/i18n" +import { FlashList, FlashListProps } from "@shopify/flash-list" + +export interface ListViewProps extends PropsWithoutRef> {} + +/** + * 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 + * @param forwardRef - React.ForwardedRef> + * @returns JSX.Element + */ +export const ListView = forwardRef( + (props: ListViewProps, forwardRef: React.ForwardedRef>) => { + const ListComponentWrapper = isRTL ? FlatList : FlashList + + return + }, +) as (props: ListViewProps, ref: React.ForwardedRef>) => JSX.Element diff --git a/boilerplate/app/components/index.ts b/boilerplate/app/components/index.ts index c65532ff4..43506380f 100644 --- a/boilerplate/app/components/index.ts +++ b/boilerplate/app/components/index.ts @@ -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" diff --git a/boilerplate/app/screens/DemoPodcastListScreen.tsx b/boilerplate/app/screens/DemoPodcastListScreen.tsx index 0f0d57606..b027c3c8c 100644 --- a/boilerplate/app/screens/DemoPodcastListScreen.tsx +++ b/boilerplate/app/screens/DemoPodcastListScreen.tsx @@ -11,7 +11,7 @@ import { View, ViewStyle, } from "react-native" -import { FlashList, type ContentStyle } from "@shopify/flash-list" +import { type ContentStyle } from "@shopify/flash-list" import Animated, { Extrapolate, interpolate, @@ -19,7 +19,7 @@ import Animated, { 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" @@ -64,7 +64,7 @@ export const DemoPodcastListScreen: FC> = safeAreaEdges={["top"]} contentContainerStyle={$screenContentContainer} > - + contentContainerStyle={$listContentContainer} data={episodeStore.episodesForList.slice()} extraData={episodeStore.favorites.length + episodeStore.episodes.length} diff --git a/boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx b/boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx index 535edf7fd..056ff27a2 100644 --- a/boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx +++ b/boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx @@ -10,10 +10,10 @@ import { View, ViewStyle, } from "react-native" -import { FlashList, type ContentStyle } from "@shopify/flash-list" +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, Screen, Text } from "../../components" import { isRTL } from "../../i18n" import { DemoTabParamList, DemoTabScreenProps } from "../../navigators/DemoNavigator" import { colors, spacing } from "../../theme" @@ -88,7 +88,7 @@ export const DemoShowroomScreen: FC> = const timeout = useRef>() const drawerRef = useRef(null) const listRef = useRef(null) - const menuRef = useRef>(null) + const menuRef = useRef>(null) const progress = useSharedValue(0) const route = useRoute>() const params = route.params @@ -181,7 +181,7 @@ export const DemoShowroomScreen: FC> = - + ref={menuRef} contentContainerStyle={$listContentContainer} estimatedItemSize={250} diff --git a/boilerplate/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx b/boilerplate/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx index 51f21a048..fb8ccd3e5 100644 --- a/boilerplate/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx +++ b/boilerplate/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx @@ -1,8 +1,7 @@ /* eslint-disable react/jsx-key, react-native/no-inline-styles */ import React from "react" import { TextStyle, View, ViewStyle } from "react-native" -import { FlashList } from "@shopify/flash-list" -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" @@ -148,7 +147,7 @@ export const DemoListItem: Demo = { description="The component can be easily integrated with your favorite list interface." > - + data={listData} renderItem={({ item, index }) => ( [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) \ No newline at end of file diff --git a/docs/Folder-Structure.md b/docs/Folder-Structure.md index ba9dcea60..e6ca2c19d 100644 --- a/docs/Folder-Structure.md +++ b/docs/Folder-Structure.md @@ -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) diff --git a/docs/README.md b/docs/README.md index c4e1b6008..9de46a48e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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) From ca9d88e17ae5bdd19f09199a7a982587533e1638 Mon Sep 17 00:00:00 2001 From: Frank Calise Date: Wed, 11 Oct 2023 09:32:42 -0400 Subject: [PATCH 5/8] chore: formatting --- docs/Components-ListView.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Components-ListView.md b/docs/Components-ListView.md index c276f9d1c..4c413e19a 100644 --- a/docs/Components-ListView.md +++ b/docs/Components-ListView.md @@ -10,4 +10,4 @@ The `ListView` component uses props from `FlashList` because they are a superset ### `estimatedItemSize` -> [Please see the FlashList documentation for more information on this prop.](https://shopify.github.io/flash-list/docs/estimated-item-size) \ No newline at end of file +> [Please see the FlashList documentation for more information on this prop.](https://shopify.github.io/flash-list/docs/estimated-item-size) From 05ceaaa90468117f8fc945c77884d83ea1115027 Mon Sep 17 00:00:00 2001 From: Frank Calise Date: Wed, 11 Oct 2023 12:24:30 -0400 Subject: [PATCH 6/8] refactor(boilerplate): ListView HOC types --- boilerplate/app/components/ListView.tsx | 25 +++++++++++++------ .../DemoShowroomScreen/DemoShowroomScreen.tsx | 8 +++--- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/boilerplate/app/components/ListView.tsx b/boilerplate/app/components/ListView.tsx index 1821ee801..3b12bc212 100644 --- a/boilerplate/app/components/ListView.tsx +++ b/boilerplate/app/components/ListView.tsx @@ -3,7 +3,9 @@ import { FlatList } from "react-native" import { isRTL } from "app/i18n" import { FlashList, FlashListProps } from "@shopify/flash-list" -export interface ListViewProps extends PropsWithoutRef> {} +export type ListViewRef = FlashList | FlatList + +export type ListViewProps = PropsWithoutRef> /** * This is a Higher Order Component meant to ease the pain of using @shopify/flash-list @@ -20,14 +22,23 @@ export interface ListViewProps extends PropsWithoutRef> {} * @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 - * @param forwardRef - React.ForwardedRef> + * @param props - FlashListProps | FlatListProps + * @param forwardRef - React.Ref> * @returns JSX.Element */ -export const ListView = forwardRef( - (props: ListViewProps, forwardRef: React.ForwardedRef>) => { + +const ListViewComponent = forwardRef( + (props: ListViewProps, ref: React.ForwardedRef>) => { const ListComponentWrapper = isRTL ? FlatList : FlashList - return + return + }, +) + +ListViewComponent.displayName = "ListView" + +export const ListView = ListViewComponent as ( + props: ListViewProps & { + ref?: React.RefObject> }, -) as (props: ListViewProps, ref: React.ForwardedRef>) => JSX.Element +) => React.ReactElement diff --git a/boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx b/boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx index 056ff27a2..2471db44a 100644 --- a/boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx +++ b/boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx @@ -13,7 +13,7 @@ import { import { type ContentStyle } from "@shopify/flash-list" import { DrawerLayout, DrawerState } from "react-native-gesture-handler" import { useSharedValue, withTiming } from "react-native-reanimated" -import { ListItem, ListView, 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" @@ -88,7 +88,7 @@ export const DemoShowroomScreen: FC> = const timeout = useRef>() const drawerRef = useRef(null) const listRef = useRef(null) - const menuRef = useRef>(null) + const menuRef = useRef>(null) const progress = useSharedValue(0) const route = useRoute>() const params = route.params @@ -181,13 +181,13 @@ export const DemoShowroomScreen: FC> = - + ref={menuRef} 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 }) => ( From 14b56328a24708e1abf516f4926a62c473cae6af Mon Sep 17 00:00:00 2001 From: Mark Rickert Date: Wed, 11 Oct 2023 10:37:55 -0600 Subject: [PATCH 7/8] chore(tests) Tests passing after adding new ListView component --- test/vanilla/ignite-generate.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/vanilla/ignite-generate.test.ts b/test/vanilla/ignite-generate.test.ts index 7353541ac..4d0198fdd 100644 --- a/test/vanilla/ignite-generate.test.ts +++ b/test/vanilla/ignite-generate.test.ts @@ -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\\" @@ -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\\" From 1c671c5243dbc4b1fce4df181126e0252d3f3ce8 Mon Sep 17 00:00:00 2001 From: Frank Calise Date: Wed, 11 Oct 2023 13:17:43 -0400 Subject: [PATCH 8/8] ci: bun version lock --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 77e6abb37..c57fa888b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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: