diff --git a/src/Navigation.js b/src/Navigation.js index ffcf2a0..87484fe 100644 --- a/src/Navigation.js +++ b/src/Navigation.js @@ -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 = () => ( { return moment(time).format('llll'); }; -const EventList = ({ eventsSectioned, refreshing, handleRefresh, handleLoadMore, showEvent, error, loading }) => { - const _renderItem = ({ item }) => ( +const renderSectionHeader = ({ section }) => ( + + {section.title} + +); + +const EventList = () => { + const { eventsSectioned, refreshing, handleRefresh, handleLoadMore, showEvent, error, loading } = useEventList(); + + const renderItem = ({ item }) => ( { showEvent(item); @@ -34,25 +43,12 @@ const EventList = ({ eventsSectioned, refreshing, handleRefresh, handleLoadMore, ); - const _renderSectionHeader = ({ section }) => { - return ( - - {section.title} - - ); - }; - - const _renderFooter = () => { - if (!loading) { - return null; - } - - return ( + const Footer = () => + !loading && ( ); - }; if (error) { return ( @@ -74,15 +70,15 @@ const EventList = ({ eventsSectioned, refreshing, handleRefresh, handleLoadMore, return ( item.id} refreshing={refreshing} onRefresh={handleRefresh} onEndReached={handleLoadMore} onEndReachedThreshould={10} initialNumToRender={10} - ListFooterComponent={_renderFooter} + ListFooterComponent={Footer} style={[styles.card, styles.list]} ListHeaderComponent={() => { return ; diff --git a/src/modules/events/EventListContainer.js b/src/modules/events/EventListContainer.js deleted file mode 100644 index bad24d5..0000000 --- a/src/modules/events/EventListContainer.js +++ /dev/null @@ -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 ( - - ); - } -} - -export default connect()(EventListContainer); diff --git a/src/modules/events/useEventList.js b/src/modules/events/useEventList.js new file mode 100644 index 0000000..e8fc476 --- /dev/null +++ b/src/modules/events/useEventList.js @@ -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; diff --git a/src/screens/EventListScreen.js b/src/screens/EventListScreen.js deleted file mode 100644 index 1e3d6dd..0000000 --- a/src/screens/EventListScreen.js +++ /dev/null @@ -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 ( - { - navigate('EventDetail', { event }); - }} - /> - ); -}; - -export default EventListScreen;