Oops! I Wrote Another Calendar
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*.
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:
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).
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 😛:
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:
- Displaying a date range: https://snack.expo.dev/@computerjazz/react-native-swipe-calendar-range
- Page interpolation: https://snack.expo.dev/@computerjazz/swipe-calendar-page-interpolator
- Infinite pager with loop: https://snack.expo.dev/@computerjazz/infinite-pager-looping
Check it out on GitHub:
*I have since found that
react-native-calendars
exports a component calledCalendarList
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