React Native Gesture Handling with ReasonML

Steven Scaffidi
g2i_co
Published in
10 min readJun 29, 2018

--

In this post, I’m going to go over leveraging ReasonML’s powerful variant types to create a React wrapper to handle React Native gestures. If you’re just getting started with Reason, take a look at my Getting Started with ReasonML and BuckleScript article.

Reason has a powerful variant system that is perfect for handling animations. Variants can model gestures (or other complex actions) that can make reasoning through creating a GestureWrapper far easier.

To get started using React Native and ReasonML, go over the following steps:

  1. Install the react-native-cli by running npm i -g react-native-cli
  2. Create a new react native app by running react-native init gestureWrapper and then cd into ./gestureWrapper
  3. Install the Reason dependencies by running npm i bs-platform reason-react bs-react-native
  4. Add the following scripts to your package.json:
"scripts": {
...
"build": "bsb -make-world -clean-world",
"watch": "bsb -make-world -clean-world -w"
}

5. Create a ./re directory and a ./re/app.re file. Add the following content:

6. Start the app. First run npm run watch and then run react-native run ios

7. Replace the index.js file with the following content:

Side Note: If you are using Windows/Linux, run the application on Android. If you are using yarn, just substitute appropriately. Note that we refer to App in ./lib/js/re/app.js. JS files are outputed to the lib directory because we have the in-source option set to false (or excluded) in our bsconfig.json. in-source set to true will generate JS files right next to your Reason files. This certainly makes it easier to refer to assets such as images and refer to the JS file. It’s a matter of preference as to what you prefer.

If everything is done correctly, you should see a white screen with “Hello World” in the top left (it might be somewhat covered up). Let’s go over how this works briefly:

  • BuckleScript is compiling the app.re file to JavaScript. The file is also being watched so if you change the app.re file it will pick up the change and recompile. Note that ReasonML will fail to compile if you have a type error or a syntax error (more on this later).
  • The index.js file is importing the compiled file and displaying the app on the screen.

Why use Reason? You get a 100% type-safe application that is easier to debug and develop. I’ll show you how this works.

Creating the GestureHandler

Let’s create the basic footprint for the GestureWrapper. We are going to create another file called ./re/gestureHandler.re. For right now, let’s just create a reducer component that accepts a child. The reducer component needs a state so we’ll skip ahead a little and add some types that we’ll be using later.

Now we’ll go over a couple of things. open BsReactNative; will allow us to use the modules in the React Native bindings without having to write BsReactNative.View. In this particular snippet, those are the View and Animated components. You should be careful about how many open statements you use in a particular file as it can cause name conflicts.

We use component in this file because we will eventually be using some React lifecycle method. We also specifically created a reducer component because we will have some state to deal with later. The reducer component requires a reducer, initialState, and a typed definition of state. Another thing to note here is that we are spreading children. That might appear odd if you are just getting started with ReasonReact. The team that developed ReasonReact chose to always wrap children in an array (since children in practice is usually an array). The key here is how Reason parses the JSX. Let’s say we had the following <MyLayout> children </MyLayout>. This actually turns into ReasonReact.element( MyLayout.make([|children|]) );. Note how children is now in an array by design. In order to make it not an array, we use the spread operator (i.e. ...children).

We also defined two types: size and coordinates. The state variable childCoordinates is defined as ref(Animated.ValueXY.jsValue) type. Why would you need a mutable property in state? On the surface that seems like an anti-pattern. In JavaScript, React components often have instance variables (i.e. this.myVar). However, this is nothing more than a way to mutate React state without triggering an update (i.e. it’s a commonly used hack). ReasonReact mandates that these variables are put in state and wrapped with ref. For our component, that’s going to be childCoordinates.

Side Note: If you’re working through this article, you’ll notice that your editor (and compiler) infer type definitions for you. One of the language’s primary strengths is the ability to infer type definitions, which guarantees 100% type-safe code without the overhead of explicitly defining types. If you need to explicitly annotate a type; take a look at line 28 above. _state: state is the annotation used. Although Reason does know the shape of state here, since there is no usage, it can’t be inferred due to a weakly typed definition of state internal to ReasonReact.

Next let’s define our variant by visualizing what our component functionality will look like. Our gestureHandler will actually prevent its child from going off of the screen. Imagine for a second that you are looking at your phone and that there is a draggable image in the middle. The possible gestures that you can take are to:

  • Drag the image to one of the 4 corners
  • Drag the image to one of the 4 sides
  • The image can move and not hit a side or a corner
  • You can touch the image without moving it

So let’s define that variant in Reason. For the sake of simplicity we’ll combine the last two gesture types into one variant.

You may ask why Corner is accepting a coordinates parameter and why TopBottom and LeftRight are accepting a float parameter. When we move our child to one of these positions, we are going to want to spring it back onto the screen (similar to how Instagram does this functionality when you’re posting). So for a corner we need to know the x, y to move it back onto the screen (similarly for the other variants). The TouchMovement variant doesn’t accept any parameters, but we may want to execute a callback.

Next we’ll create out PanResponder for our gestureHandler. Our PanResponder should be part of our component. So we are going to create another instance variable in our state. Note that initialState is simply a function that returns the state record. We can use this function to initialize our state variables and then set them.

Side Note: Although we are using a reducer component, we don’t have a need to ever re-render the component based on a state update. So our reducer will remain the same as it is now.

Note that the initialState function makes use of punning for defining the state record properties pan and childCoordinates. Punning is a very powerful feature that can be used in all types of constructs. It should not be confused with the object initializer short-hand available in ES6 JavaScript. handleRelease is a function we’ll hook up a little later.

There are a couple of syntax gotchas worth noting here. PanResponder.( is like telling Reason to use a mini open. For everything that is within the parentheses we can use PanResponder without having to put a prefix on it (i.e. PanResponder.callback becomes callback and PanResponder.create becomes create). `update is known as a polymorphic variant. Essentially onPanResponderMove accepts a variant that is not tied to types (i.e. it’s more versatile than a regular variant). Polymorphic variants are not documented in ReasonML. You can read more about them here. Note also that handleRelease is defined before our component. Unlike JavaScript, there is no hoisting in Reason. So you have to define variables before they are used.

Side Note: PanResponder.create accepts a variety of optional arguments. To tell the function we are done, we finish it off with the unit type (this is represented as ()). This is because every function in Reason is curried by default. You can read more here: https://reasonml.github.io/docs/en/function.html

Functions have to match signatures as defined in their respective modules. The type system can really help you by telling you the signature required for the callbacks. Basically a callback expects a function that takes the nativeEvent as the first argument and the gestureState as the second argument (these have been abbreviated to _e and _g).

Side Note: If you’re using VSCode, you can right click on PanResponder.create to access the panResponder.re file in the node_module. Reading through that will help you understand the types expected. In addition, the compiler will complain and specifically tell you what types are expected if you do not pass in the right types or if your function signatures don’t match what’s expected. It’s good practice in Reason to prefix an underscore on parameters not used in the function body. Without the underscore, the compiler will actually warn you that you have an unused variable.

Before moving on let’s hook up our panHandlers in our render function.

A couple of things to note about the above code-snippet. First we destructure state from our self variable. Second, notice how styles are formed. I’m not going to spend too much time on styles in this article; however, notice how everything is a function. At the end of the day, Reason is a functional language and you’ll find that most things are in fact functions.

Update your app.re to include an image as the child (just for simplicity).

Notice that we don’t have to import GestureHandler. We just use it based on the file name. Every .re file is a module that you can use throughout your program. At this point you should see a 300x300 image on the top left corner of your simulator/phone screen. Additionally, you should be able to drag it although you’ll notice it’s a little janky at this point.

Above is a visualization of the screen

We’ll primarily focus on the handleRelease part of codebase. On release, we’ll check and see where our child is on the screen and then move the child back onto the screen if part of it is off. In order to check the position of our child, we need to know the size of our screen and the size of our child. We’ll create a utility function to get the size of our screen and also let our gestureHandler accept a prop with childSize. Additionally we’ll define an additional type called size

Note that Reason always returns the last type of a function statement. In this case, the record size. Your editor should also tell you that getWindowSize has a type of unit => size

ReasonReact isn’t going to just throw a warning if you don’t pass in a childSize, it’s actually not going to compile. Required props are actually required for compilation. So let’s fix our app.re.

We need a couple more functions in order to make this work. I’m going to create these quickly and then run through them 1 by 1.

This may seem like a lot at once, but let’s break it down.

  1. resetPan calls extract offset. Per the React Native docs: Sets the offset value to the base value, and resets the base value to zero. The final output of the value is unchanged. Essentially this will allow us to move the child to point A and to point B from point A.
  2. moveChild handles the automated move of the child (if it’s off the screen). It has an optional float parameter of duration. What is |. though? The fast pipe operator takes what’s on the left and sticks it into the first argument for the function on the right. |>, another pipe operator, does the same thing except that it puts what’s on the left as the last argument for the function on the right. It’s a nicer way to write function composition rather than being worried about a million parentheses and not knowing what’s going on.
  3. handleGestureType handles the gestureType variant defined above.
  4. handleRelease is the bulk of this component and really shows off the power of Reason’s pattern matching. We defined the corners of the child first and then grab the current window size. We put the four corners into a tuple and then use a switch statement to run through all the possible variations for where the child is on the screen. The switch statement returns a curried function handleGestureType and pan is piped in to execute the action. In a nutshell, Reason really shows off its elegance here. The language is incredibly powerful at handling complex logic and making it look simple.

Lastly we have to hook up childCoordinates. We’ll need to listen to the value of pan and set childCoordinates to the value. In addition, we’ll want to make sure we remove the listener when the component unmounts. We’ll need another state variable to hold the id of our listener.

There are only a couple of changes from the previous component. willUnmount will remove the listener. In our SetPanResponder listener, we set up the listener. In addition we pass the proper arguments into the handleRelease function. So here are the final results:

I’ve written this component in both JavaScript and Reason. My Reason is not nearly as good as my JavaScript; however, I found the Reason program easier to write and debug than the JavaScript alternative. Reason’s pattern matching just makes handling logic of this component so easy. Also, I found that when I had an issue writing the Reason component, it was often a type error where the program wouldn’t compile. The compile errors in Reason are so good that fixing bugs is easy.

My source code for the above can be found here. I hope you enjoy this language as much as I’ve been enjoying it. I always love hearing feedback about my work. Let me know if you have any questions, and I’ll be happy to help out. Thanks for reading.

A special thanks to G2i for this post. For those whom are unfamiliar, G2i is a hiring platform run by engineers that matches companies with pre-vetted React, React Native, GraphQL, and native iOS/Android focused engineers you can trust. Check them out!

--

--

Full Stack @reactjs @reactnative @feathersjs @expressjs ReasonML developer | Tulane MBA @freemanschool | Husband | Rescue Dad