Usually, when developers need to implement non-trivial UI features like swipe cards, they go for the most obvious option — go on Google and find a ready-to-use package on npm.
From the business perspective, it’s a reasonable approach since it can save a lot of effort and speed-up the development process.
Yet, such out-of-the-box packages can limit or restrict certain aspects of the solution that might be crucial for your use case. For example, the library can be poorly maintained or it doesn’t meet one of the requirements.
Thus, such inconveniences can make developers opt for custom solution development for swipeable Tinder-like cards.
In this article, we’re going to show you that it’s not that hard or scary to build a custom package. As an example, we’re going to create a Tinder-like card stack view using React Native and the brand new React Native Reanimated 2 library and describe each step in detail.
To begin with, let’s list the source code of utils we are going to need for the implementation in the future. Firstly, we’ll need a card item that will be used in the stack:
Additionally, it might be a good idea to define the content for filling in the stack.
Let’s add the CardItem to the main screen so we can visualise it as well.
Here we have a static card layout with some simple content, which is good to start from. The next step is making it interactable by using React Native Reanimated library.
First of all, for creating Tinder-like swipe cards we need to link the card position to finger movement across the screen. In order to enable that, we will use a band of useAnimatedGestureHandler and PanGestureHandler. Also, useSharedValue and useAnimatedStye might be worth attention — they’re used for storing an animation state & transforming it into component styling.
The next step would be to reduce the jankiness of the default solution. The thing is, the last gesture position is not remembered, so the card jumps back to the initial position before every gesture. Let’s resolve it.
The library provides a dedicated util for this purpose, which allows us to store some additional info about the gesture — it’s called context. It allows us to fix a current problem by only a couple of additional lines. \
So, here we just initialize a gesture with the current translation animated value and then apply it to the active gesture phase.
Also it would be great to spin the card item a bit to give it a natural look and feel of Tinder-like swipe cards.
With the help of useDerivedValue hook, we can create rotation animated value, depending on current translation.
Let’s assume that the card is completely hidden when it’s translated to the width of two screens. Thus, in this position, the card will be rotated by 60 or -60 degrees respectively.
Now, we are ready to proceed to the next stage and implement stack logic.
Basically, stack implementation consists of two main features:
Thus, there will be no more than two cards that are simultaneously displayed on the screen.
The simplest step here is to hide the card by swiping it away.
The most important part here is the onEnd callback. When dragging is completed, you should check how hard a user’s swipe was.
If the velocity is sufficient, we make a card fly away (make sure to provide the right directions by obtaining the sign of the gesture's velocity), otherwise just return it back to the initial position. Animation is handled here by using the withSpring library function to create a bouncy feeling.
Furthermore, take a look at the state management of the stack into account: currentIndex is being increased on the gesture end and a card is returned to its initial position as soon as the currentIndex is changed.
Please note, you can not simply call regular functions inside React Native Reanimated worklets. Luckily, there is a runOnJS helper function that allows us to achieve the desired behavior.
We’re almost there! Next step is to animate the next item appearing to create the feeling like there is a stack of cards placed one above another.
So, here we use an absolute alignment for the next item layout and place it right below the overlay card. The next item is also tied to the animated state of the currently displayed one — the more we drag the card to the side, the more opacity and scale of the following item increase.
There is also a little trick that makes the process a little smoother. We’d recommend paying attention to useEffect: we change the index of the next item only after the current index is set and animated back to its initial position. It’s required to make the substitution of the cards completely indistinguishable and avoid blinking during items rerendering.
And last but not least, we need to provide a way to receive a callback when the card is swiped to the right or left, so the Tinder-like logic could be applied to our stack component. Moreover, it might be a good idea to encapsulate all the stack logic inside a dedicated component with a clear interface and allow item customization.
Here is how the component usage will look after this minor refactoring:
Regarding the swipe callbacks, they’re handled inside the onEnd gesture handler callback using the runOnJS library util function.
That’s all! Here is the final result — Tinder-like swipe cards. As you can see, it was not that hard to implement a custom Tinder-like stack component from scratch. Hope this article was helpful for you and you’ve enjoyed having fun with animations as much as we :)
You can find the complete source code in our git repository.
In case something feels a little complicated, you could go to the needed stage and read everything once again. Or you can reach out to us and we’ll do everything we can to help you with implementing Tinder-like swipe cards or another tech challenge!
Was it helpful?
End-to-end Testing In-App Purchases in React Native Apps: Everything You Need to Know
Building a React Native Chat App with PubNub and Gifted Chat
How to Localize your Mobile or Web App
Our clients say
They were a delight to work with. And they delivered the product we wanted. Stormotion fostered an enjoyable work atmosphere and focused on delivering a bug-free solution.
David Lesser, CEO