♻ Refactor EventList using hooks
This commit is contained in:
1 parent
3c2584eec0
commit
20b1b69d8c
5 files changed
+115
-167
No files matched your search
+2
-2
@@ -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',
|
||||
}}
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
Reference in new issue
Block a user