1
votes

I am creating a calendar component that contains 2 Views with FlatList. Each View is associated with a different data source. Specifically, View 1 will display all days in a month where View 2 will display all days in a week.

Link to video demo, button of year is for view switching

For simplicity, each month/week view is represented by a date as an array item in a bigger array.

For example, each item in the month array is a unique data in a month. An array of size 12 will have 12 unique dates of each month. Each Date() object will then be passed down to my children components for a proper rendering. The result of this particular month array is a list of complete calendar of 12 months.

Note: To improve performance, my child component will only render the current month -1, current month, and current month +1, all other months will be rendered conditionally as soon as user scrolls away.

[
    ...
    Wed May 30 2018 21:51:47 GMT+0800 (+08),
    Sat Jun 30 2018 21:51:47 GMT+0800 (+08),
    Mon Jul 30 2018 21:51:47 GMT+0800 (+08),
    ...
]

The same applies to View 2, which is the week view. Except that my data is now contained unique days in a week for all weeks in a month.

Before I implement this approach, I tried to hack something out from my child component. For instance, when users click a button to switch view, it would pass down a prop, with the current Date() object to notify the children as to conditionally render a different view. This is basically switching between a View and a FlatList. Nested FlatList was a bad idea as both lists are scrolling towards the same direction.

Not to mention that my calendar component involve jumping to selected month. Another solution I had implemented is to use only month array for both views by hacking out the indexes from keyExtractor. For example, I would reindex the list as soon as user jumps from one month to another but soon I realized this is an anti-pattern an only causes more problem.

Note: I have implemented shouldComponentUpdate in all my children components. So scrolling in just 1 View should not be a problem, size of the data source will not affect at all because only the changes happen to the -1, 0, and +1 month will be reflected. The bottleneck of my component is only happening when switching views.

Lost story short, now I have resorted to the current solution where I have 2 different data source (array) for 2 different Views in a FlatList. I have a button for the user to switch between modes. The problem is it takes some time for switching mode (setting states) as every time it re-instantiates the view by calling this prop onViewableItemsChanged from FlatList, which involve some complex calculation. Is there a better approach?

Code (Parent)

renderCalendarList = () => {
        return (
            <FlatList
                pageSize={1}
                horizontal={true}
                pagingEnabled={true}
                scrollEnabled={true}
                keyExtractor={this.keyExtractor}
                ref={(c) => this.calendarList = c}
                getItemLayout={this.getItemLayout}
                renderItem={this.renderCalendarComponent}
                onViewableItemsChanged={this.onViewableItemsChanged}
                data={(this.state.weekMode ? this.state.weekRows : this.state.rows)} />
        )
    }

switchView = () => {
        this.setState({ weekMode: !this.state.weekMode });

        // next week is going to be month view
        if (this.state.weekMode) {
            const mYClone = this.state.monthYears.slice(0);
            const selectedDay = DateUtils.formatDateInMY(this.props.selectedDay);

            for (let i = 0; i < mYClone.length; i++) {
                if (selectedDay === mYClone[i]) {
                    this.setState({ currentMonth: this.props.selectedDay })
                    this.calendarList.scrollToIndex({ animated: false, index: i });
                }
            }
        } else {             // next week is going to be week view
            const rowClone = this.state.weekRows.slice(0);

            for (let i = 0; i < rowClone.length; i++) {
                if (isSameWeek(rowClone[i], this.props.selectedDay)) {
                    this.setState({ currentMonth: rowClone[i] });
                    this.calendarList.scrollToIndex({ animated: false, index: i });
                }
            }
        }
    }

Code (Children)

render() {
        const { container, monthContainer, weekContainer } = styles;
        const { currentMonth, firstDay, style, weekMode } = this.props;
        if (!weekMode) {
            const days = DateUtils.populateMonth(currentMonth, firstDay);
            const weeks = [];
            while (days.length) {
                weeks.push(this.renderWeek(days.splice(0, 7), weeks.length));
            }
            return (
                <View style={[container, style]}>
                    <View style={[monthContainer]}>{weeks}</View>
                </View>
            )
        } else {
            const startDay = subDays(currentMonth, 3); // focus on middle
            const endDay = addDays(startDay, 6)
            const days = eachDay(startDay, endDay, 1);
            const daysToRender = [];
            days.forEach((day, dayID) => {
                daysToRender.push(this.renderDay(day, dayID))
            });
            return (
                <View style={style}>
                    <View style={[weekContainer]}>
                        {daysToRender}
                    </View>
                </View>
            )
        }
    }

Link to video demo, button of year is for view switching

I'd say you would have gotten some useful answers if you stripped the question down to what you want to actually accomplish and not included all your other code. - Ryan