React Native is a cross-platform mobile app development framework with a gentle learning curve and lots of built-in components. Because it has a very active developer community, there are also hundreds of open source third-party component libraries available for it, allowing you to create both Android and iOS apps focusing only on the apps’ core logic. Nevertheless, being able to quickly create your own custom, reusable components from scratch is still an important skill to have.
In this tutorial, I’ll show you how to create a custom React Native Calendar component using just ES6 and a few basic components offered by the framework itself.
1. Creating a New Project
To avoid installing the React Native CLI and all its dependencies on your computer, for now, I suggest you use Expo’s Snack, a free, browser-based IDE for React Native app development. If you don’t have an Expo account already, create one now.
After you log in to Expo, to create a new Snack project, switch to the Snacks tab and click on the Create a Snack link.
The IDE will take just a few seconds to create your project and prepare a preview device for it. Once it’s ready, it should look like this:
Before you proceed, make sure you delete all the sample code present in App.js.
2. Creating a New Component
To be able to use the React framework and React Native components in your project, add the following import
statements at the beginning of the App.js file:
import * as React from 'react'; import * as RN from 'react-native';
You create a custom React component by creating a class that extends the Component
class. Inside the class, you must add a method named render()
, which returns JSX code. The following code creates a component named MyCalendar
:
class MyCalendar extends React.Component { render() { return ( <RN.View> </RN.View> ); } }
In the render()
method, we’re currently returning an empty View
component. It’s going to serve as a container for all the other components of our calendar.
3. Creating Constants
The calendar component needs two string arrays: one to store the names of the months and one to store the names of the days of the week.
months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; weekDays = [ "Sun","Mon","Tue","Wed","Thu","Fri","Sat" ];
Next, we’ll need an array that stores the number of days each month has. For February, let the number be 28. We’ll write the code to handle leap years later.
nDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
4. Initialize a State
To make our calendar component interactive, we must associate a state with it. For now, we’re going to store nothing but a Date
object inside the state, initialized to today’s date.
state = { activeDate: new Date() }
The state’s, of course, mutable. When a user clicks on a different date in the calendar, we’ll be changing the state to use the new date.
5. Generating a Matrix
A matrix with seven rows and seven columns is large enough to represent any month of the year. We’ll use the first row only as a header, storing the names of the days of the week in it. To create and initialize this matrix, create a new method named generateMatrix()
.
generateMatrix() { var matrix = []; // Create header matrix[0] = this.weekDays; // More code here }
Before we start adding days to the matrix, we need to determine the day the current month begins. To do so, first get the year and month of the Date
object stored in the state. Then create a new Date
object using those values and 1
, the first day of the month. By calling the getDay()
method of this new object, you get the first day of the month.
var year = this.state.activeDate.getFullYear(); var month = this.state.activeDate.getMonth(); var firstDay = new Date(year, month, 1).getDay();
We can’t directly use the nDays
array to determine the number of days the current month has. If the month’s February, we need to manually add an extra day while dealing with leap years. Here’s how:
var maxDays = this.nDays[month]; if (month == 1) { // February if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { maxDays += 1; } }
At this point, we have all the data we need to fill in the rest of the matrix. The following code shows you how to do so using a counter, two for
loops, and two simple if
conditions:
var counter = 1; for (var row = 1; row < 7; row++) { matrix[row] = []; for (var col = 0; col < 7; col++) { matrix[row][col] = -1; if (row == 1 && col >= firstDay) { // Fill in rows only after the first day of the month matrix[row][col] = counter++; } else if (row > 1 && counter <= maxDays) { // Fill in rows only if the counter's not greater than // the number of days in the month matrix[row][col] = counter++; } } } return matrix;
Note that you need to explicitly initialize every element of the 7 x 7 matrix. If you forget to do so, the first or last row may have less than seven elements. This can lead to problems while using the map()
method to loop through the matrix.
6. Rendering a Month
Back inside the render()
method, we must now render the matrix we created. So call the generateMatrix()
method inside it.
var matrix = this.generateMatrix();
Next, let’s display the year and the name of the current month by adding a Text
component to the currently empty View
component. Optionally, you can use the style
prop to add styles to the text.
<RN.Text style={{ fontWeight: 'bold', fontSize: 18, textAlign: 'center' }}> {this.months[this.state.activeDate.getMonth()]} {this.state.activeDate.getFullYear()} </RN.Text>
We’ll be using a flexbox to render the contents of each row of the matrix. More precisely, for each row, we’ll be using a View
component with its flex
and flexDirection
parameters set to 1
and row
respectively. Additionally, to ensure that all items of the row are of the same width, we’ll set the flexbox’s justifyContent
parameter to space-around
.
Furthermore, to display the individual elements of the matrix, we’ll use Text
components again. By modifying the backgroundColor
property of the Text
components responsible for the first row’s elements, we can make the header stand out. Similarly, if you want to highlight Sundays, use the color
property of the Text
components responsible for the first column’s elements.
Our calendar should be able to highlight today’s date, or a date the user selects. Therefore, let’s associate a fontWeight
property with each Text
component. We’ll set it to bold
whenever its contents match the date in our state’s activeDate
variable.
The following code shows you how to use the map()
method as an alternative to for
loops while generating a component hierarchy for the contents of the matrix:
var rows = []; rows = matrix.map((row, rowIndex) => { var rowItems = row.map((item, colIndex) => { return ( <RN.Text style={{ flex: 1, height: 18, textAlign: 'center', // Highlight header backgroundColor: rowIndex == 0 ? '#ddd' : '#fff', // Highlight Sundays color: colIndex == 0 ? '#a00' : '#000', // Highlight current date fontWeight: item == this.state.activeDate.getDate() ? 'bold': '' }} onPress={() => this._onPress(item)}> {item != -1 ? item : ''} </RN.Text> ); }); return ( <RN.View style={{ flex: 1, flexDirection: 'row', padding: 15, justifyContent: 'space-around', alignItems: 'center', }}> {rowItems} </RN.View> ); });
To actually render the matrix, you must now include rows
in the JSX returned by the render()
method. So add the following code below the Text
component responsible for displaying the year and name of the month:
{ rows }
You may have noticed that we’ve associated an onPress
event handler with each Text
component displaying a date. We’ll use it to update the activeDate
variable whenever users click on a date. Of course, remember to ignore Text
components that are either empty or responsible for the names of the days of the week.
Accordingly, add the following method to your class:
_onPress = (item) => { this.setState(() => { if (!item.match && item != -1) { this.state.activeDate.setDate(item); return this.state; } }); };
7. Changing Months
Our calendar component shall have two buttons labeled Next and Previous. These buttons, when pressed, should allow users to move from one month to another. As you may have guessed, inside their event handlers, all we need to do is get the activeDate
object and increment or decrement its month by 1
.
Accordingly, add the following code towards the end of the JSX returned by the render()
method:
<RN.Button title="Previous" onPress={() => this.changeMonth(-1)}/> <RN.Button title="Next" onPress={() => this.changeMonth(+1)}/>
Next, create the changeMonth()
method. Inside it, you must first call the setState()
method and then call the setMonth()
method to update the activeDate
object.
changeMonth = (n) => { this.setState(() => { this.state.activeDate.setMonth( this.state.activeDate.getMonth() + n ) return this.state; }); }
8. Using the Component
Our React Native calendar component is ready. To use it, just add it to the render()
method of your App
class.
export default class App extends React.Component { render() { return <MyCalendar/>; } }
If you run your project now, you should see a calendar that looks like this:
Conclusion
You now know how to create and use a custom React Native calendar component without depending on any third-party packages. The component we created today is interactive, extensible, and can be used in any app with minimal changes. Feel free to add more styles and functionality to it.
To learn more about React Native components, refer to the official documentation. And check out some of our other posts about React Native app development!