Lately at work, my colleagues build some awesome tooling to fetch data in an easy way. It’s built around RxJS, which I previously had very little knowledge about. I saw cool ways of getting and combining data, which inspired me to build a little game with RxJS in React Native.
The game idea is to have a ball on a table and balance it using your phones tilt. I wanted to make the game pretty small to show you different ways of achieving the very same result. Here is a small video showing our plan:
Let’s get some data
First of all, we need to get the current position of the phone by using the Accelerometer component of
react-native-sensors. For this we write an App component, with the only purpose of handling the sensor creation and initializing the game:
As you can see in line 11 we set the update interval to a very low value to see if React Native can handle this many updates (it can) and to allow the motion of the device to influence the ball in the game immediately. After this groundwork is done we need to build the game itself. For this, I would like to show you multiple ways of working with RxJS streams.
Subscribe + setState
This is my first approach, it uses plain RxJS and React’s
When the component mounts we start our game: We create a stream that starts with the zero coordinates so that the ball won’t jump in the beginning (it’s initially rendered with the same coordinates). We extend the stream by adding the values up to get the current position on the screen. The values for x come with the reversed algebraic sign then what we would need for our game experience, so we just switch them by subtracting. Last but not least we set the state using the subscribe method and store the subscription so that we can clean it up on un-mount.
Winning and losing
As an extra component to the game, we need to be able to tell our users when they have fallen from the table and provide them with an opportunity to start again from scratch. For this, we want to also set the state
gameOver and render a game over screen when we lost.
We want to provide the user with a way to restart the game. As you can see in the
startNewGame method we stop the current subscription, reset the “Game Over”-state and just start a new game again. As the previous position was build in another observable we will just start in the center of the game again. Now let’s look into another way of writing the very same game.
recompose by Andrew Clark is a utility belt which provides a lot of higher order components to work with different paradigms, for example with streams. I would like to show you how you can use the
componentFromStream helper to write the entire game with the RxJS paradigms and data flow in mind.
Our last implementation stored the current game state in the React state, which triggered updates. So we have a stream of data that we grab from and put it right into our data store, which then triggers an update to the UI. This sounds like just another transformation of data and it is exactly where
componentFromStream comes to play: It transforms the props of the component to a RxJS Observable and expects you to return an Observable of React components. The cool thing is that it handles all the subscribe and unsubscribe logic for you, so you don’t need to worry about that.
In action it looks like this:
Let’s go through it step by step. First, we need to initialize our
componentFromStream function by configuring the
componentFromStreamWithConfig function with the RxJS configuration (it supports seven different streaming libraries).
Building the game itself we have a stream of props as input from the higher-order function. This stream is going to emit a value every time a property changed, for example, if we would pass another observable from our parent component. We only have one observable as the property we are interested in, it is passed in the data field. We use
switchMap in line 22 to tell RxJS whenever a property changes, take the newest version of data as the new input. Behind this line, we now have the right sensor values as Observable. As in the other example, we get the current position using
scan, to sum up the values so far.
Now we check if the player is still on the table, otherwise, we throw an error. This will be caught in line 38 and result in the
GameOverScreen to be rendered. If no error is thrown the
map in line 33 will render the game and pass the new coordinates to the ball.
As you can see the code looks much more straightforward and given a certain knowledge of RxJS it is also faster to read and comprehend. You always look at one object to understand the data flow of your component.
But RxJS has its downsides, the learning curve (at least for me) is relatively steep. For example, I couldn’t get the restart functionality working, because I lack some understanding of subjects and how to resubscribe to the stream in this scenario. But I think I’ll get there eventually and update the blog post to let you know. In the meantime, I am going to read Reactive Programming with Rxjs 5, a book my co-workers recommended me to get into the topic.