Advertisement
  1. Code
  2. Mobile Development
  3. React Native Development

Common React Native App Layouts: Calendar Page

Scroll to top
This post is part of a series called Common React Native App Layouts.
Common React Native App Layouts: Gallery Page

In this series, you'll learn how to use React Native to create page layouts commonly used in mobile apps. The layouts you'll be creating won't be functional—instead, the main focus of this series is to get your hands dirty in laying out content in your React Native apps. 

If you're new to laying out React Native apps or styling in general, check out my previous tutorial:

To follow along with this series, I challenge you to try recreating each screen by yourself first, before you read my step-by-step instructions in the tutorial. You won't really benefit much from this tutorial just by reading it! Try first before looking up the answers here. If you succeed in making it look like the original screen, compare your implementation to mine. Then decide for yourself which one is better!

In this second part of the series, you'll create the following calendar page:

calendar pagecalendar pagecalendar page

Calendar apps are used to track events and appointments added by the user. You'll find different variations in the wild, but most of them will have the same elements as a physical calendar would: the current month and year, the days in the month, and the events or appointments added by the user.

Here are a couple of examples of this type of layout:

google calendargoogle calendargoogle calendar
android calendarandroid calendarandroid calendar

Project Setup

The first step, of course, is to set up a new React Native project:

1
react-native init react-native-common-screens

Once the project is set up, open the index.android.js file and replace the default code with the following:

1
import React, { Component } from 'react';
2
import {
3
  AppRegistry
4
} from 'react-native';
5
6
import Calendar from './src/pages/Calendar';
7
8
export default class ReactNativeCommonScreens extends Component {
9
10
  render() {
11
    return (
12
      <Calendar />
13
    );
14
  }
15
16
}
17
18
AppRegistry.registerComponent('ReactNativeCommonScreens', () => ReactNativeCommonScreens);

Create a src/pages folder and create a Calendar.js file inside it.

You'll also need the react-native-vector-icons package. This is specifically used for the navigation icons as well as other icons that will be needed in the page.

1
npm install --save react-native-vector-icons

Open the android/app/build.gradle file and add a reference to the package:

1
dependencies {
2
    //rest of the dependencies are here at the top
3
    compile project(':react-native-vector-icons') //add this
4
}

Do the same with the android/settings.gradle file by adding the following at the bottom:

1
include ':react-native-vector-icons'
2
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')

Open android/app/src/main/java/com/react-native-common-screens/MainApplication.java and import the package:

1
import java.util.Arrays;
2
import java.util.List;
3
4
import com.oblador.vectoricons.VectorIconsPackage; //add this

Lastly, initialize the package: 

1
@Override
2
protected List<ReactPackage> getPackages() {
3
  return Arrays.<ReactPackage>asList(
4
      new MainReactPackage(),
5
      new VectorIconsPackage() //add this

6
  );
7
}

Creating the Calendar Page

Okay, now that you've tried to code the layout yourself (no cheating, right?), I'll show you how I built my implementation.

At first, I thought this would be the most difficult one to implement, but trust me, it's really not that complicated as long as you already know the basics. There are a couple of opportunities here to use JavaScript code to help with rendering. 

Start by including all the components and packages that you'll need:

1
import React, { Component } from 'react';
2
3
import {
4
  StyleSheet,
5
  Text,
6
  View,
7
  ScrollView
8
} from 'react-native';
9
10
import Icon from 'react-native-vector-icons/FontAwesome';
11
import { range } from 'lodash';
12
import Button from '../components/Button';

This time there's a new package which you haven't installed yet, and that is lodash. You won't really need the whole lodash library, just the range function. This is used for generating an array of numbers based on a specific range. You can install just this function by executing npm install --save lodash.range on your terminal.

Add the boilerplate code for creating pages:

1
export default class Calendar extends Component {
2
    render() {
3
    	return (
4
			<ScrollView style={styles.container}>
5
			    ...
6
			</ScrollView>

7
		);
8
	}
9
}
10
11
const styles = StyleSheet.create({
12
	container: {
13
		flex: 1
14
	}
15
});

The header has three elements in it: the button for going back to the previous page, the title of the current page, and the text showing a human-friendly representation of the currently selected date.

1
<View style={styles.header}>
2
    <Button 
3
		noDefaultStyles={true}
4
		onPress={this.press.bind(this)} 
5
		styles={{button: styles.header_item}}
6
	>
7
        <View style={styles.header_button}>
8
        	<Icon name="chevron-left" size={30} color="#FFF" />
9
        	<Text style={[styles.header_text]}> Menu</Text>

10
        </View>

11
    </Button>

12
    <View style={styles.header_item}>
13
    	<Text style={[styles.header_text, styles.text_center, styles.bold_text]}>Calendar</Text>

14
    </View>

15
	<View style={styles.header_item}>
16
    	<Text style={[styles.header_text, styles.text_right]}>Today</Text>

17
    </View>

18
</View>
calendar page initial lookcalendar page initial lookcalendar page initial look

header has a flexDirection of row so that each header_item is stacked horizontally. The same flex value is assigned to each of them so they consume equal amounts of space. text_center and text_right are used to align the text inside of those header_items to the center and right. This is done because by default they're aligned on the left-most side of their container.

1
header: {
2
    backgroundColor: '#329BCB',
3
	flexDirection: 'row',
4
	padding: 20
5
},
6
header_item: {
7
	flex: 1
8
},
9
header_button: {
10
	flexDirection: 'row'
11
},
12
text_center: {
13
	textAlign: 'center'
14
},
15
text_right: {
16
	textAlign: 'right'
17
},
18
header_text: {
19
	color: '#fff',
20
	fontSize: 20
21
},
22
bold_text: {
23
	fontWeight: 'bold'
24
},

Once the styles have been added, it should now look like this:

calendar page styled headercalendar page styled headercalendar page styled header

Next is the actual calendar, which is divided into three parts: the header, the days of the week, and the calendar days:

1
<View>
2
    <View style={styles.calendar_header}>
3
        ...
4
    </View>

5
    <View style={styles.calendar_weekdays}>
6
        ...
7
    </View>

8
    <View style={styles.calendar_days}>
9
        ...
10
    </View>

11
</View>

The calendar header allows the user to change the year and month. 

There are at least two ways this can be implemented. The first method is to treat each element as a single item and apply justifyContent: 'space-between' to its container. The second method is to group all the elements that have to do with the year and group those that have to do with the month. 

The second method is the one that's applied below. Semantically speaking, this makes much more sense because the button for navigating back a year, the year itself, and the button for navigating forward are all related, so you can treat them as a single thing by putting them in the same container. The same is true with the month controls.

1
<View style={styles.calendar_header}>
2
    <View style={styles.calendar_header_item}>
3
        <Button 
4
    		noDefaultStyles={true}
5
    		onPress={this.press.bind(this)}
6
    	>
7
            <Icon name="chevron-left" size={18} color="#333" />
8
        </Button>

9
    	<Text style={styles.calendar_header_text}>2013</Text>

10
    	<Button 
11
    		noDefaultStyles={true}
12
    		onPress={this.press.bind(this)}
13
    	>
14
            <Icon name="chevron-right" size={18} color="#333" />
15
        </Button>

16
    </View>

17
    
18
    <View style={styles.calendar_header_item}>
19
    	<Button 
20
    		noDefaultStyles={true}
21
    		onPress={this.press.bind(this)}
22
    	>
23
            <Icon name="chevron-left" size={18} color="#333" />
24
        </Button>

25
    	<Text style={styles.calendar_header_text}>November</Text>

26
    	<Button 
27
    		noDefaultStyles={true}
28
    		onPress={this.press.bind(this)}
29
    	>
30
            <Icon name="chevron-right" size={18} color="#333" />
31
        </Button>

32
    </View>

33
</View>
calendar page added calendar headercalendar page added calendar headercalendar page added calendar header

From there, you can apply the same technique to those two groups of components in the same line. To add spaces between the two buttons (back and forward) and the label, we use justifyContent: 'space-between'. We use alignItems: 'center' to nudge all the elements inside it towards the center. Finally, we add left and right padding to add more space between the two groups.

1
calendar_header: {
2
    flexDirection: 'row'
3
},
4
calendar_header_item: {
5
	flex: 1,
6
	flexDirection: 'row',
7
	justifyContent: 'space-between',
8
	alignItems: 'center',
9
	paddingTop: 20,
10
	paddingRight: 40,
11
	paddingLeft: 40
12
},
13
calendar_header_text: {
14
	fontWeight: 'bold',
15
	fontSize: 20
16
},
calendar page added calendar header stylescalendar page added calendar header stylescalendar page added calendar header styles

Next are the weekdays. We use a function to render these because it's best to use some JavaScript code to render all the elements.

1
<View style={styles.calendar_weekdays}>
2
    { this.renderWeekDays() }
3
</View>

So instead of having seven View or Text components rendering each day of the week, you can just have an array containing the days of the week. You can then iterate through those days using the Array.map() function. For each iteration, render a Text component that shows the day. 

1
renderWeekDays() {
2
    let weekdays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
3
	return weekdays.map((day) => {
4
		return (
5
			<Text key={day} style={styles.calendar_weekdays_text}>{day.toUpperCase()}</Text>

6
		);
7
	});
8
}

Note that in the code above, the toUpperCase() function is used to convert all the letters of each day to uppercase. React Native doesn't come with the text-transform CSS property, so this is the only way to achieve uppercase letters aside from manually using uppercase strings.

calendar page added calendar week dayscalendar page added calendar week dayscalendar page added calendar week days

Here's the styling for the calendar header:

1
calendar_weekdays_text: {
2
    flex: 1,
3
	color: '#C0C0C0',
4
	textAlign: 'center'
5
},
calendar page styled calendar week dayscalendar page styled calendar week dayscalendar page styled calendar week days

The calendar days also uses a function for rendering the days:

1
<View style={styles.calendar_days}>
2
    { this.renderWeeks() }
3
</View>

The renderWeeks() function uses the range() function in lodash to generate an array containing the days from the last month and the days of the current month. Those two arrays are then merged together. 

However, you can't directly use the resulting array as the data source for the calendar days. That's because if you simply loop through the items and output a Text component for each day, there won't be any distinction between each week. You already know that to make each calendar day inline, you need to apply flexDirection: 'row' to its container. So applying it to a single container would result in having all the calendar days placed in a single line. 

This means you need to have a separate container for each week. The question is how. Again, there are at least two ways to accomplish this. 

The first method is to have a variable store how many days are currently outputted and then add a conditional statement that will render an opening <View> every time the variable contains 0 and a closing </View> every time it's 7. Once it's 7, reset it back to 0. This is the most straightforward method.

But I'll use a different method here. Below, the getWeeksArray() function is used to implement it. This function accepts the array of days and groups them into arrays containing seven days each. From there, you can loop through each of those arrays to render the week container. Then for each iteration, you again loop through the days inside the week to render the days. This is what the renderDays() function does.

1
renderWeeks() {
2
    let past_month_days = range(27, 31);
3
	let this_month_days = range(1, 30);
4
5
	let days = past_month_days.concat(past_month_days, this_month_days);
6
	let grouped_days = this.getWeeksArray(days);
7
8
	return grouped_days.map((week_days, index) => {
9
		return (
10
			<View key={index} style={styles.week_days}>
11
				{ this.renderDays(week_days) }				
12
			</View>

13
		);
14
	});
15
}

Here's the getWeeksArray() function:

1
getWeeksArray(days) {
2
    var weeks_r = [];
3
	var seven_days = [];
4
	var count = 0;
5
	days.forEach((day) => {
6
	  count += 1;
7
	  seven_days.push(day);
8
	  if(count == 7){
9
	    weeks_r.push(seven_days)
10
	    count = 0;
11
	    seven_days = [];
12
	  }
13
	});
14
	return weeks_r;
15
}

And here's the renderDays() function:

1
renderDays(week_days) {
2
    return week_days.map((day, index) => {
3
		return (
4
			<Button 
5
				label={day}
6
				key={index} 
7
				onPress={this.press.bind(this)} 
8
				styles={{button: styles.day, label: styles.day_text}}
9
				noDefaultStyles={true}
10
			/>	

11
		);
12
	});
13
}
calendar page added calendar dayscalendar page added calendar dayscalendar page added calendar days

Add the styling for each week (week_days) and day (day and day_text):

1
week_days: {
2
    flexDirection: 'row'
3
},
4
day: {
5
	flex: 1,
6
	backgroundColor: '#F5F5F5',
7
	padding: 17,
8
	margin: 2
9
},
10
day_text: {
11
	textAlign: 'center',
12
	color: '#A9A9A9',
13
	fontSize: 25
14
},
calendar page add calendar days stylingcalendar page add calendar days stylingcalendar page add calendar days styling

Next is the note added by the user for the currently selected day and the selected date and time. Again, it's better to group elements according to their purpose rather than how they're placed in the page. Certainly all these elements are related, so we'll place them inside the same container. But on a closer look, you'll start to see that you can group them further: the actual note and the selected date. With that in mind, here's the markup that you'll end up with:

1
<View style={styles.notes}>
2
    <View style={styles.notes_notes}>
3
		<Text style={styles.notes_text}>Riding my bike around the neighborhood.</Text>

4
	</View>

5
	<View style={[styles.notes_selected_date]}>
6
		<Text style={styles.small_text}>8:23 PM</Text>

7
		<Text style={styles.big_text}>14</Text>

8
		<View style={styles.inline}>
9
			<Icon name="bicycle" size={20} color="#CCC" />
10
			<Text style={styles.small_text}> THURSDAY</Text>

11
		</View>

12
	</View>

13
</View>
calendar page add notescalendar page add notescalendar page add notes

The selected date occupies less space than the note, so you have to apply a bigger flex value to the notes. flex: 3 and flex: 1 are used in this case, which means that the notes consume 3/4 of the available space and the selected date consumes 1/4. You can also use decimals (0.75 and 0.25) if that makes more sense to you. What's important is to pick a standard and stick to it. alignItems: 'flex-end' is used on notes_selected_date so that all its children will be aligned to the right. This is needed because by default they're aligned to the left.  

1
notes: {
2
    marginTop: 10,
3
	padding: 20,
4
	borderColor: '#F5F5F5',
5
	borderTopWidth: 1,
6
	borderBottomWidth: 1,
7
	flexDirection: 'row',
8
	backgroundColor: '#FAFAFA'
9
},
10
notes_notes: {
11
	flex: 3
12
},
13
notes_text: {
14
	fontSize: 18
15
},
16
notes_selected_date: {
17
	flex: 1,
18
	alignItems: 'flex-end',
19
	flexDirection: 'column'
20
},
21
small_text: {
22
	fontSize: 15
23
},
24
big_text: {
25
	fontSize: 50,
26
	fontWeight: 'bold'
27
},
28
inline: {
29
	flexDirection: 'row'
30
},
calendar page added styling to logscalendar page added styling to logscalendar page added styling to logs

Lastly, we add the logs, which are very similar to those in the previous tutorial, so I'll leave it to you to figure out how the layout is achieved!

1
<View style={styles.logs}>
2
    <View>
3
		<Text style={styles.log_text}>Create New Entry</Text>

4
		<Text style={styles.log_subtext}>On Thursday, November 14</Text>

5
	</View>

6
	<Button 
7
		noDefaultStyles={true}
8
		onPress={this.press.bind(this)}
9
	>
10
		<Icon name="chevron-right" size={30} color="#CCC" />
11
	</Button>

12
</View>

Here are the styles:

1
logs: {
2
    flexDirection: 'row',
3
	justifyContent: 'space-between',
4
	alignItems: 'center',
5
	padding: 20,
6
	borderColor: '#F5F5F5',
7
	borderBottomWidth: 1
8
},
9
log_text: {
10
	fontSize: 25
11
},
12
log_subtext: {
13
	fontSize: 18
14
}

Conclusion

That's it! In this tutorial you've created a calendar page. We've made a nice calendar layout for an app, and I've shown you how JavaScript code can be used to compensate for some of the limitations of Flexbox. 

As you have seen, we needed a way to limit the number of days in a row to just seven days. Flexbox doesn't have a way to specify this, so we used JavaScript to reconstruct the original array of days in such a way that they're divided into groups containing seven days each. From there, all we had to do was to wrap each group inside a View and then apply flexDirection: 'row' to make each of them render in their own row.

In an upcoming tutorial, you'll learn how to implement the layout commonly used in gallery pages. In the meantime, check out some of our other tutorials on React Native and Flexbox.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.