♻ Refactor EventList using hooks

This commit is contained in:
nikolark committed 2022-08-01 21:21:40 +02:00
1 parent 3c2584eec0
commit 20b1b69d8c
5 files changed
+115 -167

No files matched your search

+2 -2
View File
@@ -4,7 +4,7 @@ import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import Icon from 'react-native-vector-icons/SimpleLineIcons';
import EventListScreen from './screens/EventListScreen';
import EventList from './modules/events/EventList';
import EventDetail from './modules/events/EventDetail';
import Membership from './modules/membership/Membership';
@@ -17,7 +17,7 @@ const EventStackScreen = () => (
<EventStack.Navigator initialRouteName="EventList">
<EventStack.Screen
name="EventList"
component={EventListScreen}
component={EventList}
options={{
title: 'Program',
}}
+16 -20
View File
@@ -4,6 +4,7 @@ import React from 'react';
import moment from 'moment';
import 'moment/locale/nb';
import useEventList from './useEventList';
import theme from '../../theme';
moment.locale('nb');
@@ -12,8 +13,16 @@ const _formatTime = (time) => {
return moment(time).format('llll');
};
const EventList = ({ eventsSectioned, refreshing, handleRefresh, handleLoadMore, showEvent, error, loading }) => {
const _renderItem = ({ item }) => (
const renderSectionHeader = ({ section }) => (
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>{section.title}</Text>
</View>
);
const EventList = () => {
const { eventsSectioned, refreshing, handleRefresh, handleLoadMore, showEvent, error, loading } = useEventList();
const renderItem = ({ item }) => (
<TouchableOpacity
onPress={() => {
showEvent(item);
@@ -34,25 +43,12 @@ const EventList = ({ eventsSectioned, refreshing, handleRefresh, handleLoadMore,
</TouchableOpacity>
);
const _renderSectionHeader = ({ section }) => {
return (
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>{section.title}</Text>
</View>
);
};
const _renderFooter = () => {
if (!loading) {
return null;
}
return (
const Footer = () =>
!loading && (
<View style={{ paddingVertical: 20 }}>
<Spinner color="#f58220" />
</View>
);
};
if (error) {
return (
@@ -74,15 +70,15 @@ const EventList = ({ eventsSectioned, refreshing, handleRefresh, handleLoadMore,
return (
<SectionList
sections={eventsSectioned}
renderItem={_renderItem}
renderSectionHeader={_renderSectionHeader}
renderItem={renderItem}
renderSectionHeader={renderSectionHeader}
keyExtractor={(item) => item.id}
refreshing={refreshing}
onRefresh={handleRefresh}
onEndReached={handleLoadMore}
onEndReachedThreshould={10}
initialNumToRender={10}
ListFooterComponent={_renderFooter}
ListFooterComponent={Footer}
style={[styles.card, styles.list]}
ListHeaderComponent={() => {
return <View style={styles.listHeader} />;
-128
View File
@@ -1,128 +0,0 @@
import { connect } from 'react-redux';
import React, { Component } from 'react';
import Config from 'react-native-config';
import moment from 'moment';
import 'moment/locale/nb';
import EventList from './EventList';
import { fetchWithTimeout } from '../../utils';
moment.locale('nb');
/* Ref: https://medium.com/react-native-development/how-to-use-the-flatlist-component-react-native-basics-92c482816fe6 */
class EventListContainer extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
events: [],
eventsSectioned: [],
page: 1,
totalPages: 0,
error: null,
refreshing: false,
};
}
_formatDate(time) {
const m = moment(time);
if (moment().year() === m.year()) {
return m.format('dddd D. MMM');
}
return m.format('ll');
}
_toSectionFormat(events) {
const sectioned = {};
events.forEach((event) => {
const title = this._formatDate(event.start_time);
if (title in sectioned) {
sectioned[title].push(event);
} else {
sectioned[title] = [event];
}
});
const sectionedList = [];
Object.entries(sectioned).forEach(([title, section]) => {
sectionedList.push({ data: section, title });
});
return sectionedList;
}
handleRefresh = () => {
this.setState(
{
page: 1,
refreshing: true,
totalPages: 1,
},
() => {
this.fetchEvents();
}
);
};
handleLoadMore = () => {
if (this.state.page >= this.state.totalPages || this.state.loading) {
return;
}
this.setState(
{
page: this.state.page + 1,
},
() => {
this.fetchEvents();
}
);
};
fetchEvents() {
const { page } = this.state;
const url = `${Config.EVENT_API_URL}/wp-json/wp/v2/events?page=${page}&future=1`;
this.setState({ loading: true });
fetchWithTimeout(url)
.then((res) => {
this.setState({ totalPages: parseInt(res.headers.get('x-wp-totalpages'), 10) });
return res.json();
})
.then((res) => {
this.setState({
events: page === 1 ? res : [...this.state.events, ...res],
eventsSectioned:
page === 1 ? this._toSectionFormat(res) : this._toSectionFormat([...this.state.events, ...res]),
error: res.error || null,
loading: false,
refreshing: false,
});
})
.catch((error) => {
this.setState({ error, loading: false });
});
}
componentDidMount() {
this.fetchEvents();
}
showEvent = (item) => {
this.props.showEvent(item);
};
render() {
return (
<EventList
showEvent={this.showEvent}
handleRefresh={this.handleRefresh}
handleLoadMore={this.handleLoadMore}
{...this.state}
/>
);
}
}
export default connect()(EventListContainer);
+97
View File
@@ -0,0 +1,97 @@
import { useEffect, useState } from 'react';
import Config from 'react-native-config';
import moment from 'moment';
import 'moment/locale/nb';
import { useNavigation } from '@react-navigation/native';
import { fetchWithTimeout } from '../../utils';
moment.locale('nb');
const formatDate = (time) => {
const m = moment(time);
return moment().year() === m.year() ? m.format('dddd D. MMM') : m.format('ll');
};
/* Ref: https://medium.com/react-native-development/how-to-use-the-flatlist-component-react-native-basics-92c482816fe6 */
const useEventList = () => {
const { navigate } = useNavigation();
const [loading, setLoading] = useState(false);
const [events, setEvents] = useState([]);
const [eventsSectioned, setEventsSectioned] = useState([]);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(0);
const [error, setError] = useState(null);
const [refreshing, setRefreshing] = useState(false);
const toSectionFormat = (freshEvents) => {
const sectioned = {};
freshEvents.forEach((event) => {
const title = formatDate(event.start_time);
if (title in sectioned) {
sectioned[title].push(event);
} else {
sectioned[title] = [event];
}
});
return Object.entries(sectioned).map(([title, section]) => ({ data: section, title }));
};
const handleRefresh = () => {
setPage(1);
setRefreshing(true);
setTotalPages(1);
fetchEvents();
};
const handleLoadMore = () => {
if (page >= totalPages || loading) {
return;
}
setPage(page + 1);
fetchEvents();
};
const fetchEvents = async () => {
const url = `${Config.EVENT_API_URL}/wp-json/wp/v2/events?page=${page}&future=1`;
setLoading(true);
let eventData;
try {
const res = await fetchWithTimeout(url);
setTotalPages(parseInt(res.headers.get('x-wp-totalpages'), 10));
eventData = await res.json();
} catch (err) {
setError(err);
setLoading(false);
return;
}
setEvents(page === 1 ? eventData : [...events, ...eventData]);
setEventsSectioned(page === 1 ? toSectionFormat(eventData) : toSectionFormat([...events, ...eventData]));
setError(eventData.error || null);
setLoading(false);
setRefreshing(false);
};
useEffect(() => {
fetchEvents();
}, []);
return {
showEvent: (event) => {
navigate('EventDetail', { event });
},
handleRefresh,
handleLoadMore,
error,
events,
eventsSectioned,
refreshing,
loading,
};
};
export default useEventList;
-17
View File
@@ -1,17 +0,0 @@
import React from 'react';
import { useNavigation } from '@react-navigation/native';
import EventListContainer from '../modules/events/EventListContainer';
const EventListScreen = () => {
const { navigate } = useNavigation();
return (
<EventListContainer
showEvent={(event) => {
navigate('EventDetail', { event });
}}
/>
);
};
export default EventListScreen;