Skip to content

Commit

Permalink
Improve performance of events processing (hoangnm#27)
Browse files Browse the repository at this point in the history
* Add props to set the hours in display, and the start hour

Disable event-pressing if no handler is passed
Fix typo in README.md

* Fix lint errors

* Make event sorting more efficient

Move sorting to WeekView, to avoid sorting multiple times
Add memoization to the sorting process

* Make event displaying more efficient

Avoid double renders in Events Component

* Fix memoize-one dependency
  • Loading branch information
pdpino authored Jun 2, 2020
1 parent c00232c commit e35d625
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 189 deletions.
114 changes: 62 additions & 52 deletions example/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,64 +11,74 @@ import {SafeAreaView, StyleSheet, StatusBar, Alert} from 'react-native';

import WeekView from 'react-native-week-view';

const App: () => React$Node = () => {
const selectedDate = new Date();
const generateDates = (hours, minutes) => {
const date = new Date();
date.setHours(date.getHours() + hours);
if (minutes != null) {
date.setMinutes(minutes);
}
return date;
const generateDates = (hours, minutes) => {
const date = new Date();
date.setHours(date.getHours() + hours);
if (minutes != null) {
date.setMinutes(minutes);
}
return date;
};

const sampleEvents = [
{
id: 1,
description: 'Event 1',
startDate: generateDates(0),
endDate: generateDates(2),
color: 'blue',
},
{
id: 2,
description: 'Event 2',
startDate: generateDates(1),
endDate: generateDates(4),
color: 'red',
},
{
id: 3,
description: 'Event 3',
startDate: generateDates(-5),
endDate: generateDates(-3),
color: 'green',
},
];

class App extends React.Component {
state = {
events: sampleEvents,
selectedDate: new Date(),
};
const events = [
{
id: 1,
description: 'Event 1',
startDate: generateDates(0),
endDate: generateDates(2),
color: 'blue',
},
{
id: 2,
description: 'Event 2',
startDate: generateDates(1),
endDate: generateDates(4),
color: 'red',
},
{
id: 3,
description: 'Event 3',
startDate: generateDates(-5),
endDate: generateDates(-3),
color: 'green',
},
];
const onEventPress = ({id, color, startDate, endDate}) => {

onEventPress = ({id, color, startDate, endDate}) => {
Alert.alert(
`event ${color} - ${id}`,
`start: ${startDate}\nend: ${endDate}`,
);
};
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={styles.container}>
<WeekView
events={events}
selectedDate={selectedDate}
numberOfDays={3}
onEventPress={onEventPress}
headerStyle={styles.headerStyle}
headerTextColor="#fff"
formatDateHeader="MMM D"
hoursInDisplay={12}
startHour={8}
/>
</SafeAreaView>
</>
);
};

render() {
const {events, selectedDate} = this.state;
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={styles.container}>
<WeekView
events={events}
selectedDate={selectedDate}
numberOfDays={3}
onEventPress={this.onEventPress}
headerStyle={styles.headerStyle}
headerTextColor="#fff"
formatDateHeader="MMM D"
hoursInDisplay={12}
startHour={8}
/>
</SafeAreaView>
</>
);
}
}

const styles = StyleSheet.create({
container: {
Expand Down
8 changes: 7 additions & 1 deletion example/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3517,6 +3517,11 @@ mem@^4.0.0:
mimic-fn "^2.0.0"
p-is-promise "^2.0.0"

memoize-one@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==

merge-stream@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1"
Expand Down Expand Up @@ -4314,8 +4319,9 @@ react-is@^16.12.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"

"react-native-week-view@file:..":
version "0.0.8"
version "0.0.11"
dependencies:
memoize-one "^5.1.1"
moment "^2.19.3"
prop-types "^15.7.2"

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
],
"homepage": "https://github.com/hoangnm/react-native-week-view#readme",
"dependencies": {
"memoize-one": "^5.1.1",
"moment": "^2.19.3",
"prop-types": "^15.7.2"
},
Expand Down
94 changes: 32 additions & 62 deletions src/Events/Events.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import React, { Component } from 'react';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import {
View,
Dimensions,
} from 'react-native';
import moment from 'moment';
import memoizeOne from 'memoize-one';

import Event from '../Event/Event';
import { TIME_LABEL_HEIGHT, CONTAINER_HEIGHT } from '../utils';
import {
TIME_LABEL_HEIGHT,
CONTAINER_HEIGHT,
calculateDaysArray,
DATE_STR_FORMAT,
} from '../utils';

import styles, { CONTENT_OFFSET } from './Events.styles';

Expand All @@ -16,45 +22,11 @@ const MINUTES_IN_HOUR = 60;
const TIME_LABEL_WIDTH = 40;
const EVENTS_CONTAINER_WIDTH = SCREEN_WIDTH - TIME_LABEL_WIDTH - 35;

class Events extends Component {
getEventsByNumberOfDays = (numberOfDays, events, selectedDate) => {
// total stores events in each day of numberOfDays
// example: [[event1, event2], [event3, event4], [event5]], each child array
// is events for specific day in range
const total = [];
let initial = 0;
if (numberOfDays === 7) {
initial = 1;
initial -= moment().isoWeekday();
}
for (let i = initial; i < (numberOfDays + initial); i += 1) {
// current date in numberOfDays, calculated from selected date
const currenDate = moment(selectedDate).add(i, 'd');

// filter events that have startDate/endDate in current date
let filteredEvents = events.filter((item) => {
return currenDate.isSame(item.startDate, 'day') || currenDate.isSame(item.endDate, 'day');
});

filteredEvents = filteredEvents.map((item) => {
let { startDate } = item;
// if endDate is in next day, set starDate to begin time of current date (00:00)
if (!currenDate.isSame(startDate, 'day')) {
startDate = currenDate.startOf('day').toDate();
}
return {
...item,
startDate,
};
});
total.push(filteredEvents);
}
return total;
};

class Events extends PureComponent {
getStyleForEvent = (item) => {
const startHours = moment(item.startDate).hours();
const startMinutes = moment(item.startDate).minutes();
const startDate = moment(item.startDate);
const startHours = startDate.hours();
const startMinutes = startDate.minutes();
const totalStartMinutes = (startHours * MINUTES_IN_HOUR) + startMinutes;
const top = this.minutesToYDimension(totalStartMinutes);
const deltaMinutes = moment(item.endDate).diff(item.startDate, 'minutes');
Expand Down Expand Up @@ -112,26 +84,29 @@ class Events extends Component {
return EVENTS_CONTAINER_WIDTH / numberOfDays;
};

sortEventByDates = (events) => {
const sortedEvents = events.slice(0)
.sort((a, b) => {
return moment(a.startDate)
.diff(b.startDate, 'minutes');
});
return sortedEvents;
};
processEvents = memoizeOne((eventsByDate, initialDate, numberOfDays) => {
// totalEvents stores events in each day of numberOfDays
// example: [[event1, event2], [event3, event4], [event5]], each child array
// is events for specific day in range
const dates = calculateDaysArray(initialDate, numberOfDays);
const totalEvents = dates.map((date) => {
const dateStr = date.format(DATE_STR_FORMAT);
return eventsByDate[dateStr] || [];
});
const totalEventsWithPosition = this.getEventsWithPosition(totalEvents);
return totalEventsWithPosition;
});

render() {
const {
events,
eventsByDate,
initialDate,
numberOfDays,
selectedDate,
times,
onEventPress,
} = this.props;
const sortedEvents = this.sortEventByDates(events);
let totalEvents = this.getEventsByNumberOfDays(numberOfDays, sortedEvents, selectedDate);
totalEvents = this.getEventsWithPosition(totalEvents);
const totalEvents = this.processEvents(eventsByDate, initialDate, numberOfDays);

return (
<View style={styles.container}>
{times.map(time => (
Expand Down Expand Up @@ -163,16 +138,11 @@ class Events extends Component {

Events.propTypes = {
numberOfDays: PropTypes.oneOf([1, 3, 5, 7]).isRequired,
events: PropTypes.arrayOf(Event.propTypes.event),
onEventPress: PropTypes.func,
selectedDate: PropTypes.instanceOf(Date),
times: PropTypes.arrayOf(PropTypes.string),
eventsByDate: PropTypes.objectOf(PropTypes.arrayOf(Event.propTypes.event)).isRequired,
initialDate: PropTypes.string.isRequired,
hoursInDisplay: PropTypes.number.isRequired,
};

Events.defaultProps = {
events: [],
selectedDate: new Date(),
times: PropTypes.arrayOf(PropTypes.string).isRequired,
onEventPress: PropTypes.func,
};

export default Events;
5 changes: 4 additions & 1 deletion src/Events/Events.styles.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { StyleSheet } from 'react-native';
import { Dimensions, StyleSheet } from 'react-native';

const { width: SCREEN_WIDTH } = Dimensions.get('window');
const GREY_COLOR = '#E9EDF0';
export const CONTENT_OFFSET = 16;

const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 16,
width: SCREEN_WIDTH - 60,
},
timeRow: {
flex: 0,
Expand Down
23 changes: 4 additions & 19 deletions src/Header/Header.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Text, View } from 'react-native';
import moment from 'moment';

import { getFormattedDate, getCurrentMonth } from '../utils';
import { getFormattedDate, getCurrentMonth, calculateDaysArray } from '../utils';

import styles from './Header.styles';

const getColumns = (numberOfDays, selectedDate) => {
const columns = [];
let initial = 0;
if (numberOfDays === 7) {
initial = 1;
initial -= moment().isoWeekday();
}
for (let i = initial; i < (numberOfDays + initial); i += 1) {
let date = moment(selectedDate);
date = date.add(i, 'd');
columns.push(date.toDate());
}
return columns;
};

const getFontSizeHeader = (numberOfDays) => {
if (numberOfDays > 1) {
return 12;
Expand Down Expand Up @@ -82,7 +66,8 @@ const Title = ({ numberOfDays, selectedDate, textColor }) => { // eslint-disable
const WeekViewHeader = ({
numberOfDays, selectedDate, formatDate, style, textColor
}) => {
const columns = getColumns(numberOfDays, selectedDate);
const columns = calculateDaysArray(selectedDate, numberOfDays);

return (
<View style={[styles.container, style]}>
<Title numberOfDays={numberOfDays} selectedDate={selectedDate} textColor={textColor} />
Expand All @@ -103,4 +88,4 @@ WeekViewHeader.defaultProps = {
formatDate: 'MMM D',
};

export default WeekViewHeader;
export default React.memo(WeekViewHeader);
26 changes: 26 additions & 0 deletions src/Times/Times.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
import { View, Text } from 'react-native';
import styles from './Times.styles';
import { TIME_LABEL_HEIGHT } from '../utils';

const Times = ({ times }) => {
return (
<View style={styles.columnContainer}>
{times.map(time => (
<View
key={time}
style={[styles.label, { height: TIME_LABEL_HEIGHT }]}
>
<Text style={styles.text}>{time}</Text>
</View>
))}
</View>
);
};

Times.propTypes = {
times: PropTypes.arrayOf(PropTypes.string).isRequired,
};

export default React.memo(Times);
Loading

0 comments on commit e35d625

Please sign in to comment.