-
Notifications
You must be signed in to change notification settings - Fork 0
FlatList
Naoto0213 edited this page Jun 27, 2022
·
10 revisions
Object,ArrayなどをList化する
使用ライブラリ: https://reactnative.dev/docs/flatlist
ストーリーブック:
FlatList
import React, { useCallback, useMemo } from "react";
import {
FlatList as OriginalFlatList,
FlatListProps,
ListRenderItem,
StyleSheet,
View,
} from "react-native";
import { SIDE_PADDING, TOP_PADDING } from "src/components/layouts/ScreenWrapper";
import { Split } from "src/components/layouts/Space";
import NotFound from "src/components/molecules/NotFound";
import { SMALL_PX, X_LARGE_PX } from "src/dict/space";
import { isNotEmpty } from "src/utils";
type BaseFlatListProps<T> = Omit<
FlatListProps<T>,
"ListHeaderComponent" | "ListFooterComponent" | "keyExtractor"
>;
export type Props<T> = {
dividerSize?: number;
// FIXME: loading の入れ忘れをチェックするために required にしておく
loading: boolean;
renderLoading?: ListRenderItem<number>;
renderLoadingNum?: number;
ListHeaderComponent?: React.ReactNode;
ListFooterComponent?: React.ReactNode;
disableScreenWrapperAdjust?: boolean;
screenWrapperAdjust?: { side: number; top: number };
nested?: boolean;
keyExtractor: FlatListProps<T>["keyExtractor"] | keyof T;
notFound: React.ReactNode;
} & BaseFlatListProps<T>;
export const useExtendedFlatListProps = <T,>({
dividerSize = SMALL_PX,
loading,
renderLoading: renderLoadingBase,
renderLoadingNum = 3,
screenWrapperAdjust = { side: SIDE_PADDING, top: TOP_PADDING },
disableScreenWrapperAdjust,
nested,
keyExtractor: keyExtractorBase = defaultKeyExtractor,
notFound = "",
...baseProps
}: Props<T>): FlatListProps<T> => {
const screenWrapper = useMemo(
() => (disableScreenWrapperAdjust || nested ? { side: 0, top: 0 } : screenWrapperAdjust),
[disableScreenWrapperAdjust, nested],
);
const style = useMemo(
() => [
styles.flatList,
{ marginHorizontal: -screenWrapper.side, marginTop: -screenWrapper.top },
baseProps.style,
],
[baseProps.style, screenWrapper],
);
const ItemSeparatorComponent = useMemo(
() =>
baseProps.ItemSeparatorComponent ??
(() => <View style={{ width: dividerSize, height: dividerSize }} />),
[baseProps.ItemSeparatorComponent, dividerSize],
);
const loadingData = useMemo(
() => Array.from({ length: renderLoadingNum }, (v, k) => k as any as T),
[renderLoadingNum],
);
const renderLoading: ListRenderItem<number> = useMemo(
() => (props) => (renderLoadingBase && renderLoadingBase(props)) ?? null,
[renderLoadingBase],
);
const contentContainerStyle = useMemo(
() => [
!nested && { paddingBottom: X_LARGE_PX },
{ marginHorizontal: screenWrapper.side, marginTop: screenWrapper.top },
baseProps.contentContainerStyle,
],
[baseProps.contentContainerStyle, screenWrapper, nested],
);
const keyExtractor: FlatListProps<T>["keyExtractor"] = useCallback(
(item: T, index: number) =>
typeof keyExtractorBase === "function"
? keyExtractorBase(item, index)
: `${item[keyExtractorBase as keyof T]}`,
[keyExtractorBase],
);
const ListHeaderComponent = (
<>
{baseProps.ListHeaderComponent && (
<>
{baseProps.ListHeaderComponent}
<Split vertical size={dividerSize} />
</>
)}
{!loading &&
!isNotEmpty(baseProps.data) &&
(typeof notFound === "string" ? <NotFound message={notFound} /> : notFound)}
</>
);
const ListFooterComponent = (baseProps.ListFooterComponent || null) && (
<>
<Split vertical size={dividerSize} />
{baseProps.ListFooterComponent}
</>
);
const props: FlatListProps<T> = {
...baseProps,
style,
keyExtractor,
ItemSeparatorComponent,
contentContainerStyle,
ListHeaderComponent,
ListFooterComponent,
nestedScrollEnabled: baseProps.nestedScrollEnabled ?? nested,
};
if (loading)
return {
...props,
data: loadingData,
// @ts-ignore
renderItem: renderLoading,
// @ts-ignore
keyExtractor: numberKeyExtractor,
getItemLayout: undefined,
getItem: undefined,
};
return props as FlatListProps<T>;
};
const FlatList = React.memo(
React.forwardRef(<T,>(baseProps: Props<T>, ref: React.Ref<OriginalFlatList>) => {
const props = useExtendedFlatListProps(baseProps);
return <OriginalFlatList {...props} ref={ref} />;
}),
);
const numberKeyExtractor = (i: number) => String(i);
const defaultKeyExtractor: Exclude<FlatListProps<any>["keyExtractor"], undefined> = (v, k) =>
`${k}`;
// ScreenWrapper の使用下で
// スクロールバーの位置を調整するために, renderItem 側で Margin を再度付与
const styles = StyleSheet.create({
flatList: {},
});
export default FlatList;FlatListComponents
const FlatListComponents: React.FC = () => {
return (
<FlatList
data={[0, 1, 2, 3]}
loading={false}
keyExtractor={(v) => `${v}`}
renderItem={({ item }) => (
<Card>
<H1>タイトル</H1>
<P>item: {item}</P>
</Card>
)}
/>
);
};const FlatListComponents: React.FC = () => {
return (
<FlatList
data={[0, 1, 2, 3]}
loading={false}
keyExtractor={(v) => `${v}`}
renderItem={({ item }) => (
<Card>
<H1>タイトル</H1>
<P>item: {item}</P>
</Card>
)}
/>
);
};
-
dataにList化するObjectを定義 -
renderItemでReactNodeで出力 -
notFound時がある場合、notFoundにReactNodeを定義
| Property | Description |
|---|---|
| data | Object,Array |
| renderItem | dataに定義したObjectをReactNodeで出力 |
| notFound | notfound時の表示 |
DefaultProps:
https://reactnative.dev/docs/flatlist
足りないものはストーリーブックに追加後に追加
| Property | Description | Default | types | required |
|---|---|---|---|---|
data |
配列を定義 | Object |
true | |
renderItem |
配列の中身を出力する | React.ReactNode |
true | |
notFound |
notfound時の表示 | null | React.ReactNode |
true |
ListHeaderComponent |
Listの一番上に表示されるコンポーネント | null | React.ReactNode |
false |
dividerSize |
dividerの大きさ | 8 | number |
false |
loading |
fetch中はloading画面を表示するか | false | boolean |
false |
disableScreenWrapperAdjust |
dividerの大きさ | false | boolean |
false |
nested |
fetch中はloading画面を表示するか | false | boolean |
false |