A quick guide on how to create a universal week view list in React Native and have it typed with TypeScript so that a day in the week could consist of different types of data.
Assignment
Imagine we have a React Native app and let’s say that we wanted to display a list of events in a form of the week view, where each week should have its header with start date or number of the week and exactly 7 days. Each day could have a number of events (no events at all or a few). Additionally, it would be useful to make it a generic component to be able to reuse it if we want to display a week view with another type of data e.g. todos.
So let’s take a closer look!
Prerequisites
React Native app (v0.63.2 used in this guide)
TypeScript (3.9.7)
Moment and lodash.range (just for simplicity, can be replaced)
Prepare the Ground
Generic Types
First, let’s start with a few types. SectionListDay<T> represents the day in the week. And SectionListWeek<T>represents the week, seven days. Since we need to be able to distinguish weeks one from another, we also have a title for each week. It is just a date that can be formatted in the UI later.
types.ts
export type SectionListDay<T> = { [k in keyof T]: T[k] } & { date: Moment };
export type SectionListWeek<T> = {
title: Moment;
data: Array<SectionListDay<T>>;
};
types.ts
export type SectionListDay<T> = { [k in keyof T]: T[k] } & { date: Moment };
export type SectionListWeek<T> = {
title: Moment;
data: Array<SectionListDay<T>>;
};
By the way, our model for the data is the following.
types.ts
export interface Event {
id: number;
name: string;
start: Moment;
end: Moment;
}
types.ts
export interface Event {
id: number;
name: string;
start: Moment;
end: Moment;
}
Transform
Then, let’s create our generic build/transform function which takes an object of data, e.g. events and outputs basically a section which then would perfectly match the SectionList’s sections prop. The helper might not be perfect, feel free to play with it. In conjunction with proper testing, it will do its work. The principal idea here is to be able to assign different types of data to the day and make the whole module typed.
import moment, { Moment } from 'moment';
import { range } from 'lodash';
const DAYS_IN_WEEK = 7;
/**
*
* Builds a week of 7 days of provided structure e.g. `{ events: Event[] }` starting from `startDate`.
* There is a requirement for items to have the `start` property, so it could be moved to its day.
*/
export function buildSectionListData<T extends { [key: string]: Array<{ start: Moment }> }>(
items: T,
startDate: Moment
): SectionListWeek<T> {
const week = {
title: startDate,
data: range(DAYS_IN_WEEK).map((day) => {
// Increment startDate by one day with every new day
Using the above, let’s now define screen-specific types which would be the foundation of the list component.
types.ts
export type EventsDay = SectionListDay<{ events: Event[] }>;
export type EventsWeek = SectionListWeek<EventsDay>;
types.ts
export type EventsDay = SectionListDay<{ events: Event[] }>;
export type EventsWeek = SectionListWeek<EventsDay>;
And finally, we use our types and transformer to create the Events screen. For the sake of simplicity, I make the component receive events through the prop but of course, they could be fetched and transformed dynamically with the useEffect hook, for example.
Just look how simple it is. The whole complex component is now correctly typed. Moreover, we could effortlessly reuse the same transformation logic.
Next Steps
Based on that, we could go further, for example if the day should consist of user-created todos along with events. It is just as easy, define new day and week types and provide raw data { events, todos } to the build function.
export type ScheduleDay = SectionListDay<{ events: Event[]; todos: Todo[] }>;
export type ScheduleWeek = SectionListWeek<ScheduleDay>;
const scheduleSection = buildSectionListData({ events, todos }, startDate);
export type ScheduleDay = SectionListDay<{ events: Event[]; todos: Todo[] }>;
export type ScheduleWeek = SectionListWeek<ScheduleDay>;
const scheduleSection = buildSectionListData({ events, todos }, startDate);
The above is arguably very logical. A week (section) consists of 7 days, and a day contains its own events. One more cool side of it is that we don’t need to hassle with custom logic for week headers or inventing how data should be divided into weeks. Although I haven’t covered the pull-to-refresh feature, navigating by weeks, etc., it could be introduced without an any issue. The UI could be anything and is on you. It shouldn’t be difficult at all even to create a higher-level generic ListScreen component! Feel free to experiment with all this. The most important thing is that it is now scalable, testable and really usable.