In this post, I'll share some details around design decisions and mistakes I made while working on React Native Modal, a Modal component library for React Native.
I hope that sharing my thoughts may help other new open-source maintainers to avoid such errors.
This post focuses on a React Native library, but it's not strictly related to React Native. It's more about generic design and maintainability decisions.
This should be a beginner-friendly post. Seasoned open source maintainers are probably already familiar with the topics explored here.
2016 — Open Sourcing a React Native library
I started using React Native in late 2015 at my daily job. Back then React Native was still in its infancy.
In early 2016 I open-sourced a tiny React Native library to enhance the capabilities of React Native's built-in Modal component. The built-in Modal is a thin API layer to present content above an enclosing view. It is a low-level API, in the sense that it just offers a way to "show" something, but it's still up to the developer to handle the styling, animation, and behavior of whatever they present.
The goals of my library were to:
- Show a backdrop beneath the modal.
- Animate the modal entrance/exit.
- Allow dismissing the modal on backdrop touch.
The initial surface area of the project was small, and it didn't allow many customization options.
The entire project was 86 lines of code.
2021 - React Native Modal, today
It's August 2021 now, and the modal component is known as react-native-modal
.
Thanks to all its contributors, react-native-modal
now offers a ton of customization options and features: it's swipeable, scrollable, user-friendly, and it patches a few quirks of the native modal implementation. And, as expected, the codebase has grown exponentially.
react-native-modal
has more than 4.5k stars on GitHub, and has/had many contributors.
I feel like most of its success is due to:
- Being one of the first few React Native libs that worked as a simple drop-in replacement to lower level APIs.
- Having a SEO-friendly (and, unfortunately, misleading) name.
- Gaining some good exposure while being part of the official React Native Community organization, when it included higher-level APIs and components.
The sore point
It's not all fun and games, though.
Since 2020, react-native-modal
development has slowed down.
Around two years ago, I moved to a different role at my company, and I'm not using React Native anymore. Additionally, I have several other side-projects I'm trying to maintain. So, I'm not able to actively develop new features in react-native-modal
, nor provide the same grade of support I used to.
I'm still keeping an eye on important issues and ensuring it stays compatible across React Native updates. Also, some folks are helping from time to time (thank you all, especially @ancyrweb), and I'm still open to onboarding new people on the project.
But we're far from being as active as we were a couple of years ago.
Overall, I think react-native-modal
is still a nice library. But I also feel it could be even better if I made different decisions in the past.
Mistakes have been made
Besides a few small side projects, react-native-modal
is my first "serious" open-source library.
While developing and maintaining it, I had a lot of fun, learned new things, and made some poor decisions.
In hindsight, to me, most of these poor decisions sound "obviously" wrong now. But, hey, that's how experience works and how you learn.
And that's also why I think that sharing these decisions and the thought process behind them might help other new open source maintainers.
Mistake #1: Not having a clear goal and direction
I created the first version of react-native-modal
because I needed its features in my daily job. Then, I decided to open source it.
I didn't open source it to solve a specific problem. I just wanted to give back to the community and (maybe?) receive contributions (e.g., bug reports, new features).
And it worked!
There was an issue, though: react-native-modal
didn't have a "real" goal.
Without a clear direction, react-native-modal
quickly became a huge catch-all modal solution. We often added features on top of features just because the native modal API exposed by React Native wasn't satisfying 100% of the use cases. All of it while trying to catch up with React Native, Android, and iOS updates.
We were (and are) doing the exact opposite of KISS (Keeping It Simple, Stupid). And the codebase suffers from this. The more features we want to support, the more conditions and edge cases we need to cover.
Mistake #2: Not saying "no" enough
When someone spends hours to package an excellent pull request that introduces a new feature, it's hard to say "no" to it.
But, sometimes, I shouldn't have been scared to say it.
Quoting Jeff Geerling:
Feel free to say 'no' when a PR doesn't meet your standards. So many projects get derailed by accepting too many new features without evaluating them for long-term maintainability, and it's a problem that's avoided by a simple two-letter word.
Not having a clear goal makes it much harder to say "no" to contributions.
Especially for pull requests with new features, I should have been more forward-looking and rejected them when they weren't 100% aligned with the (unfortunately, blurry) project architecture.
Mistake #3: Using a misleading library name
For the first few months, this library was named react-native-animated-modal
.
Then, after asking for permission, I moved it to react-native-modal
.
In retrospect, this was a mistake.
A generic name like react-native-modal
sets some wrong expectations because it sounds like the "official" modal component of React Native.
Especially for newcomers, it can be unintuitive that this is just a wrapper on top of React Native's built-in modal component. From time to time, I still see issues opened in the react-native-modal
repo mentioning problems pertinent to just the React Native built-in modal.
In my ideal world, the react-native-modal
library name should be used only by the built-in React Native modal (if extracted from the core repository) or by a thin abstraction layer on top of it.
If anyone on the React Native team is reading this post: If you need the npm namespace, feel free to DM me 👍
Mistake #4: Hanging back on hard decisions
Another thing I regret doing is hesitating too much on a hard decision: Moving react-native-modal
to a full JavaScript implementation.
We started thinking about using "just JavaScript" because the most common issues reported in react-native-modal
are tied to the behavior of the native modal implementation that we can't change.
To clarify: I'm not talking about issues of the React Native built-in modal. I'm talking about the native Android and iOS modal. Things like stacking multiple modals on top of each other weren't natively supported by the Android and iOS OS until a couple of years ago.
Moving react-native-modal
to a complete JavaScript implementation would unlock a ton of customization options.
But not being backed by a native API introduces new complexities to the tables:
What API should we expose to allow the modal to sit at the root element of the app performantly?
How do we achieve native-looking modals (especially with the upcoming — now available — iOS modality flow)?
What about accessibility?
In PROPOSAL: JS version of react-native-modal #145, I think I did a good job explaining the issue and suggesting a solution. We got a ton of helpful feedback and ideas. But I procrastinated — and never actually started working on it. I regret not making a sound decision and not leading the initiative with a complete proof of concept.
Mistake #5: Depending on third-party libraries for core features
The last mistake I wanted to mention is relying too much on third-party libraries.
Specifically, in my case, react-native-animatable
.
React Native Animatable is a great library that allows defining transitions and animations in a declarative fashion by abstracting the React Native animated
API. In 2015/2016, react-native-animatable
was the way to sprinkle animations on top of React Native apps. Mad props to @oblador for building it.
react-native-animatable
powers all react-native-modal
's animation. Users can pick any animation exposed by react-native-animatable
and apply it to the enter/exit state of the modal with a single line of code.
Time has passed, though. React Native Animatable is still a good solution by today's standards, but it's not as performant nor configurable as other modern options.
Nowadays, there are several new ways to animate views more efficiently in React Native. Between the good-old React Native animated
API, Reanimated/Reanimated 2, Moti (which can almost be a drop-in replacement for React Native Animatable), and Lottie, adding silk-smooth native animations to a React Native app has never been easier.
I don't regret picking React Native Animatable, but I do regret using a high-level API to customize the modal animation.
Most entrace/exit animations for modals are just a combination of translation and opacity interpolations. They could have been easily covered by a lower-level API (like React Native animated
), and we would have avoided playing this catch-up game with the newer APIs.
Summary
I hope this post doesn't sound like a post-mortem.
I want to clarify that you can/should still use React Native Modal, if it fits your use-case.
It works well, even on the latest version of React Native.
There are some known bugs here and there, but they're all fixable. And I'm still open to onboarding new people on the project (just DM me, and/or start contributing to the repo).
My goal with this post is just to share some of my experiences, hoping they can be helpful to new open-source maintainers.
Thanks to the React Native maintainers, and to everyone who contributed to React Native Modal so far!