/
Blog
/
OpenInvest — React Native Case Study

OpenInvest — React Native Case Study

A case study on how OpenInvest and G2i partnered to accelerate mobile development with React Native.

By Isaiah Grey

OpenInvest is a platform that is transforming mainstream socially responsible investing and shareholder participation into a natural, everyday part of activism. With a web application already in production, the OpenInvest team sought out React Native expertise from G2i to accelerate development to bring their platform to the mobile market.

Challenges

UI Complexity

Financial data is complex to display even on laptop or external monitors, which presents some interesting challenges building a user interface for mobile device screens. Some core functionality of OpenInvest’s mobile application was to allow users to view their portfolio diversity, the companies they invested in, including and excluding companies to invest in, and shareholder proxy voting. Each of these components proved a challenge to use the space available, without sacrificing user experience efficiently.

Proxy Voting

Shareholder participation is a crucial element to the OpenInvest platform. The goal of the proxy voting scenes were to allow users to quickly make informed decisions on company issues, which align with their values. A design was set forth that a user would be able to vote by swiping left or right to indicate a yes or no to a company vote.

The complexity of the proxy voting UI was in the design of having a swipeable card, that revealed a custom view that was determined by the swipe direction.

Most swipeable card components that are out there currently do not support this type of behavior out of the box or were not readily modifiable to accommodate the needed functionality. This brought about the decision of creating a custom swipeable card component.

Configuring A Component To Respond to Touches

Our custom swipeable card component was built using the React Native `View` component, taking advantage the React Native Gesture Responder System.

We configured our `View` component to become a Touch Responder, by setting two-component prop functions, `onStartShouldSetResponder` and `onMoveShouldSetResponder`.

Both functions are used to determine whether a touch or movement of the View should “catch” events and respond to them. The onStartShouldSetResponder prop function is called on the initial touch of the View, while onMoveShouldSetResponder executes on every touch move on the View.

Since our custom component is the only movable element in the main proxy voting scene, we just set each prop function mentioned before to return a value of true; this will enable the view to be the touch responder for all touch events starting from the first touch and every touch following.

```bash
<View
    onMoveShouldSetResponder={() => true}
    onStartShouldSetResponder={() => true}>
    {/* SWIPEABLE CARD COMPONENT */}
</View>
```

Component Drag Animation

Next, by setting the `onResponderMove` function prop, we can react to the user swipe motion and set the x-coordinate position of our card element using the touch-move event data.

```bash
<View
     style={{
         transform: [{ translateX: this.state.card.x }]
      }}
      onMoveShouldSetResponder={() => true}
      onStartShouldSetResponder={() => true}
      onResponderMove={evt => {
          this.setState({
             card: {
                 x: evt.nativeEvent.pageX
     }
   });
 }}
>
 {/* SWIPEABLE CARD COMPONENT */}
</View>
```

Part of the card swipe effect is a small rotation in the swiped direction; this gives our card swipe a more natural feeling of a real card in a deck.

We calculate the rotation of the card by determining how far past the center of the screen a card has been swiped. Once we derive the percent of the screen width that has swiped, we take the max possible degree rotation and apply the percentage to this figure. The result gives us the number of degrees, negative or positive, to rotate our card component.

In our case, we chose 15 degrees as the max rotation in the swipe direction. Given a screen that is 300px wide, if the last recorded x-coordinate of a user touch event for the card is 75, then the user has swiped the card 50% to the left of the horizontal screen center. With 50% of the possible swipe direction made, this means the current card rotation would be -7.5 degrees (negative for the left swipe direction).

```bash
<View
     style={{
       transform: [{ translateX: this.state.card.x }, { rotate:
this.state.card.rotation }]
   }}
   onMoveShouldSetResponder={() => true}
   onStartShouldSetResponder={() => true}
   onResponderMove={evt => {
       const maxRotationDegs = 15;
       const lastSwipeXCoord = evt.nativeEvent.pageX;
       const { width: screenWidth } = Dimensions.get("window");
      
 
        const distanceSwiped = lastSwipeXCoord - screenWidth / 2;
       const cardRotation = distanceSwiped / screenWidth *
maxRotationDegs;    


        this.setState({
          card: {
              rotation: `${cardRotation}deg`,
              x: evt.nativeEvent.pageX
     }
   });
 }}
>
 { /* SWIPEABLE CARD COMPONENT */ }
</View>
```

The result is an animation of the view to the new x-coordinate, with the corresponding rotation based on the next update of the component render. We apply this transform to the card for its position and rotation via a style using the transformproperty.

Managing Update Frequency & Performance

The `onResponderMove` event fires after every touch move event, so it was essential to throttle the function call to every 100ms for mobile performance. In addition to limiting the update frequency of our card position, we animated the values for the position and rotation to make a clean, smooth card swiping experience. (For brevity, the incorporation of using React Native's `Animated` was left out.)

Revealing A Custom View For Each Swipe Direction

The custom `View` revealed when a user swiped was animated to a red or green background depending on the swipe direction. The swipe direction determined by the positive or negative sign value of the swipe distance less half the screen width. The swipeable card, and the underlying `View` originate from the same container View within `the` component, and are absolute position one over the other. Since the Card is animated and draggable, the swipe/drag of the top card reveals the card underneath.

```bash
<View
   style={{
      transform:   [
          {  translateX: this.state.card.x },
          { rotate: this.state.card.rotation }
        ]
   }}
   onMoveShouldSetResponder={() => true}
   onStartShouldSetResponder={() => true}
   onResponderMove={evt => {
       const maxRotationDegs = 15;
       const lastSwipeXCoord = evt.nativeEvent.pageX;
       const { width: screenWidth } = Dimensions.get("window");


       const distanceSwiped = lastSwipeXCoord - (screenWidth / 2);
      const swipeDirection = lastSwipeXCoord < 0 ? "left" : "right";
       const cardRotation = (distanceSwiped / screenWidth) *
maxRotationDegs;    


       this.setState({
         swipeDirection: swipeDirection,
         card: {
             rotation: `${cardRotation}deg`,
            x: evt.nativeEvent.pageX
     }
   });
 }}>
 { /* SWIPEABLE CARD COMPONENT */ }
</View>
<View
    style={{
       backgroundColor: (
            this.state.swipeDirection === "right" ? "green" : "red"
     )
 }}>
 { /* CUSTOM REVEALED VIEW COMPONENT */ }
<View>
```

The Result

The end result of our custom component for Proxy Voting encompassed the great user experience the OpenInvest team designed, and allow users to easily vote on the issues they care about for the companies they’ve chosen to invest in.

Across the board, the G2i team was instrumental in helping the OpenInvest team build the components that handled the collection and visualization of investment data to allow users to make informed investment decisions for their portfolio.

Related Articles