Oops! I Wrote Another Calendar

Daniel Merrill
Async
Published in
7 min readJan 11, 2022

--

Introducing react-native-infinite-pager, a new infinitely-swipeable pager component for React Native, and react-native-swipe-calendar, the use case that sparked it.

It was a Friday afternoon and I’d spent the day deep in my node_modules putting together another patch for react-native-calendars. The patch enabled a smooth swipe between months and it basically worked, but it was a hack and the gestures didn’t quite feel right. I was complaining about it to my coworker Chris, and wishing there was a better option*.

As of this writing, react-native-calendars hasn’t been updated since v1.17.0 was released in February 2018

Chris responded with a good bit of perspective:

This perspective comes from experience. He authored one of the first popular calendar components for React Native, which he deprecated when react-native-calendars picked up steam. He elaborated:

The “workshop” I referred to was a Reanimated 2 Layout Animation webinar, where an elegant new API for animating layouts was demoed.

I was inspired—their new API is simple on the surface, and requires very little extra code if you’re happy with the out-of-the-box animations. But you can also peel away its layers to reveal finer-grained ways to write animations. Each layer down is a little more complex, and a little more powerful.

And I meant it when I said I didn’t want any part in creating another calendar package, but these things have a way of sneaking up on you, don’t they? Over the weekend I tinkered with some ideas for a new “infinite pager” component written with Reanimated 2, and then once that was up and running, turning it into a calendar component was just a few steps away. The following Monday morning I sent Chris a message:

Here it is:

https://github.com/computerjazz/react-native-swipe-calendar

Let me explain how this happened.

The Infinite Pager

The infinite pager was most the interesting part for me. Most of the time, when a carousel component is written, some kind of “finite” pager is used under the hood, driven by a FlatList or ScrollView with paging enabled. An array of items is passed in and each item is rendered as a slide. This works well if you know every item in advance (and if you don’t care about looping behavior).

But a calendar is infinite in both directions. In order to fake this infinite behavior with a ScrollView in my hacky react-native-calendars patch, I had to scroll back to the middle on each swipe and reset state so that middle item became the “current” item. So, the first step was figuring out how to build an infinitely-swipeable pager component without any hacks.

I’ve written a lot of gesture-based animations with Reanimated and React Native, and I decided there was no reason why it wouldn’t be possible to build a pager that was infinite in both directions using a PanGestureHandler and few Animated.View components.

A few hours later I had the groundwork for what later became react-native-infinite-pager. Here’s what it looks like in action:

Each page gets an index prop that can be used to drive its content, as in the gif above. Looping is possible using some modulo math:

// Constrains the index between 0 and `length` 
// (in this case 0-2)
function getModuloIndex(index: number, length: number) {
let indexMod = index % length
if (indexMod < 0) indexMod = length + indexMod
return indexMod
}

There’s no limit to how many pages the above app could go up or down to: you could keep swiping to page 1,000,000 or more if you swiped long enough, without any performance issues, and there’s no ScrollView trickery required.

The “active page” is determined by a single animated value, driven by the swipe gesture (or by calling a method on the pager ref). The pager also renders pages to the left and the right of the active page. When a page is translated beyond that left/right buffer, it is no longer rendered.

This is also the basis for my performance claim above—even though it feels infinite, it’s only rendering three pages at a time (but “infinite pager” is a catchier name than “three-at-a-time-pager”). As long as those three pages aren’t rendering anything crazy, performance shouldn’t be an issue.

The Calendar

Once the infinite pager was up and running, turning it into a calendar was just a matter of generating a date for each month by using the page index as the offset.

The calendar is all driven by utility methods provided by date-fns. Each page receives the index from the infinite pager and uses the addMonths function with that index to get the month date for that page. Then, date-fns utilities are used to generate all the days in that month, which are rendered out to components.

I wanted users to be able to customize the calendar as much as possible without introducing bloat into the package, so I took a note from Reanimated and provide a few different layers of customization.

The simplest way to customize is by passing a theme object, which can customize colors, font sizes, and font family for each part of the calendar. If you need to do anything more elaborate than that, it’s time to roll your own components, which I strived to make as easy as possible.

Where react-native-calendars attempts to cover as many use cases as possible, I decided on the opposite approach: to only cover a minimal use case but make it easy to bring your own components. This makes the package much leaner, and hopefully easier to maintain.

This is why I think you’ll like react-native-swipe-calendar:

It’s simple

The fully-functional proof-of-concept was about 500 lines of code in total. Customizing fonts and colors is done with a single minimal config object. When I plugged react-native-swipe-calendar into the app I’m developing it shaved off ~300 lines of code (granted a lot of this was the patch I was able to delete).

PR diff when I replaced react-native-calendars with react-native-swipe-calendar

It’s performant

Reanimated 2 is amazing. It’s intuitive to write, and gives you that “native UI thread feel”. There’s now no delay when swiping between months.

It has sensible defaults, and is easily customized

Use the theme object if the basic layout works for you, swap in your own components if it doesn’t.

It can do this 😛:

Snack: https://snack.expo.dev/@computerjazz/swipe-calendar-page-interpolator

If you want to go animation-crazy, I exposed pageInterpolator and animationConfig props. The pageInterpolator prop will replace the default interpolations of the infinite pager, allowing you to tweak styles like translateX, scale, rotation, and opacity, or whatever other styles you want to animate. The animationConfig prop will modify the default page transition spring animation parameters (damping, mass, etc).

So, if you’re working on a “Classic Hollywood Film” app and need a calendar that evokes the “pages flying off the wall” trope, look no further 😉.

A few example Snacks:

Check it out on GitHub:

*I have since found that react-native-calendars exports a component called CalendarList that allows a user to swipe between months. It uses a FlatList under the hood as described above, but instead of allowing infinite swiping using the “scroll to middle” hack, it constrains calendars to a 4-year time range.

Async builds high performance, reliable, and cost-effective applications by combining technical expertise and deep knowledge of industry trends.

For more information on development services, visit asy.nc

--

--