HomeScreen - complete refactor

This commit is contained in:
Dawid Wysokiński 2021-05-15 19:21:14 +02:00
parent 25a11e9dde
commit efe69b927f
12 changed files with 132 additions and 42 deletions

View File

@ -1,51 +1,19 @@
import React, { useMemo, useState } from 'react';
import { NetworkStatus, useQuery } from '@apollo/client';
import { useVariables } from 'libs/native-base';
import { Query, QueryProfessionsArgs } from 'libs/graphql';
import { QUERY_PROFESSIONS } from './queries';
import React, { useState } from 'react';
import { Container, Content, Spinner } from 'native-base';
import List from './components/List/List';
import { Container } from 'native-base';
import Header from './components/Header/Header';
import ModeSelector, { Mode } from './components/ModeSelector/ModeSelector';
import NetworkConnectionAlert from './components/NetworkConnectionAlert/NetworkConnectionAlert';
import Professions from './components/Professions/Professions';
const HomeScreen = () => {
const [search, setSearch] = useState('');
const [mode, setMode] = useState(Mode.All);
const variables = useVariables();
const { loading, data, refetch, networkStatus, error } = useQuery<
Pick<Query, 'professions'>,
QueryProfessionsArgs
>(QUERY_PROFESSIONS, {
fetchPolicy: 'cache-and-network',
variables: { sort: ['name ASC'] },
notifyOnNetworkStatusChange: true,
});
const professions = useMemo(() => {
return (data?.professions.items ?? []).filter(
profession => profession.qualifications.length > 0,
);
}, [data]);
return (
<Container>
<Header onSearch={setSearch} />
<ModeSelector mode={mode} onChangeMode={setMode} />
{loading && professions.length === 0 ? (
<Content padder>
<Spinner color={variables.brandPrimary} size="large" />
</Content>
) : (
<List
professions={professions}
refreshing={networkStatus === NetworkStatus.refetch}
onRefresh={refetch}
mode={mode}
search={search}
/>
)}
<NetworkConnectionAlert error={error} />
<Professions mode={mode} search={search} />
</Container>
);
};

View File

@ -43,6 +43,7 @@ const Header = ({ onSearch }: HeaderProps) => {
value={search}
ref={inputRef}
allowFontScaling={false}
clearButtonMode="always"
/>
</Item>
<View>

View File

@ -0,0 +1,65 @@
import React, { forwardRef, useCallback } from 'react';
import {
FlatList,
FlatListProps,
RefreshControl,
StyleSheet,
} from 'react-native';
import ListItem, { ListItemProps } from './ListItem';
import ListEmpty from './ListEmpty';
import ListLoading from './ListLoading';
export type Item = ListItemProps;
export interface ListProps
extends Pick<
FlatListProps<Item>,
'refreshing' | 'onRefresh' | 'contentContainerStyle'
> {
items: Item[];
loading?: boolean;
}
const noop = () => {};
const MyList = forwardRef<FlatList<Item>, ListProps>(
({ items, refreshing, onRefresh, loading, ...rest }: ListProps, ref) => {
const renderItem = useCallback(({ item }: { item: Item }) => {
return <ListItem {...item} />;
}, []);
const keyExtractor = useCallback(item => item.id, []);
console.log('render');
return (
<FlatList
data={items}
ref={ref}
renderItem={renderItem}
ListEmptyComponent={loading ? null : <ListEmpty />}
ListFooterComponent={loading ? <ListLoading /> : null}
ListFooterComponentStyle={styles.footerWrapper}
keyExtractor={keyExtractor}
maxToRenderPerBatch={5}
onEndReachedThreshold={0.75}
refreshControl={
<RefreshControl
refreshing={refreshing ?? false}
onRefresh={onRefresh ?? noop}
/>
}
initialNumToRender={10}
{...rest}
/>
);
},
);
const styles = StyleSheet.create({
footerWrapper: {
flexGrow: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
export default MyList;

View File

@ -1,12 +1,12 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import { H1, Content } from 'native-base';
import { H1, View } from 'native-base';
const ListEmpty = () => {
return (
<Content padder contentContainerStyle={styles.wrapper}>
<View padder style={styles.wrapper}>
<H1 style={styles.heading}>Nie znaleziono żadnej kwalifikacji</H1>
</Content>
</View>
);
};

View File

@ -0,0 +1,10 @@
import React from 'react';
import { Spinner } from 'native-base';
import { useVariables } from 'libs/native-base';
const ListLoading = () => {
const variables = useVariables();
return <Spinner size="large" color={variables.brandPrimary} />;
};
export default ListLoading;

View File

@ -0,0 +1,31 @@
import { useMemo } from 'react';
import { NetworkStatus, useQuery } from '@apollo/client';
import { Query, QueryProfessionsArgs } from 'libs/graphql';
import { QUERY_PROFESSIONS } from './queries';
const useProfessions = () => {
const { loading, data, refetch, networkStatus, error } = useQuery<
Pick<Query, 'professions'>,
QueryProfessionsArgs
>(QUERY_PROFESSIONS, {
fetchPolicy: 'cache-and-network',
variables: { sort: ['name ASC'] },
notifyOnNetworkStatusChange: true,
});
const professions = useMemo(() => {
return (data?.professions.items ?? []).filter(
profession => profession.qualifications.length > 0,
);
}, [data]);
return {
loading: professions.length === 0 && loading,
refetch,
professions,
refetching: networkStatus === NetworkStatus.refetch,
error,
};
};
export default useProfessions;

View File

@ -0,0 +1,17 @@
import { useRef } from 'react';
import { FlatList } from 'react-native';
import { Item } from './List/List';
import { useUpdateEffect } from 'react-use';
import { Mode } from '../ModeSelector/ModeSelector';
const useScrollTopOnSearchOrModeChange = (search: string, mode: Mode) => {
const listRef = useRef<FlatList<Item>>(null);
useUpdateEffect(() => {
listRef.current?.scrollToOffset({ offset: 0, animated: false });
}, [search, mode]);
return listRef;
};
export default useScrollTopOnSearchOrModeChange;

View File

@ -14,9 +14,7 @@ const Stack = createStackNavigator<AppStackParamList>();
const AppStack = createStackNavigator<AppStackParamList>();
const AppScreens = () => (
<AppStack.Navigator
screenOptions={{ animationEnabled: false, headerShown: false }}
>
<AppStack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name={Screen.Home} component={HomeScreen} />
<Stack.Screen name={Screen.Test} component={TestScreen} />
</AppStack.Navigator>