by

What we will be building

We’re going to build an app that has a chart and a list of crypto coins along with their prices and price changes in last 24 hours. Each coin when tapped on updates the chart with its price history.

In this part of the series, we’ll get started, install all dependencies, and build the chart part of the app. And we’ll continue building the rest of the app in the next parts.

For your reference, the final code for the app we’re building can be found in this GitHub repo.

Creating a new project

Let’s start off by creating a new app. Open Terminal App and run these commands to initialize a new project.

react-native init CoinsChart;
cd CoinsChart;

Installing dependencies

First of all, let’s install a couple of dependencies.

  • Redux for storing and managing the data fetched from the API.
npm install --save redux react-redux 
  • Redux Thunk for calling asynchronous Redux actions.
npm install --save redux-thunk
  • Lodash for working with arrays and objects.
npm install --save lodash
  • Numeral for formatting prices.
npm install --save numeral
  • React Native Vector Icons for rendering up and down arrow icons.
npm install --save react-native-vector-icons;
react-native link react-native-vector-icons;
  • Babel plugin for enabling ES7 decorators.
npm install --save-dev babel-plugin-transform-decorators-legacy

Update .babelrc

Next, we need to update .babelrc to enable ES7 decorators.

  • Open .babelrc file and add transform-decorators-legacy plugin that we just installed:
{
  "presets": ["react-native"],
  "plugins": ["transform-decorators-legacy"]
}

ART

ART is a vector drawing API that we’ll be using for drawing the chart.

  • Let’s install it and link the dependencies first.
npm install art --save;
react-native link art;
  • Open CoinsChart.xcodeproj from ios folder.
  • Find ART.xcodeproj file in node_modules/react-native/Libraries/ART folder.
  • Drag ART.xcodeproj into Libraries folder.

  • Link libART.a with binary.

Flow

Flow is a static type checker for JavaScript. We’ll be using it to type hint React component, Redux actions, reducers, etc. Flow improves code readability, finds errors instantly, and allows you to refactor code more confidently, which especially important as you app grows bigger.

npm install --save-dev [email protected] babel-cli babel-preset-flow;
  • Open .babelrc file and add flow to presets:
{
  "presets": ["react-native", "flow"],
  "plugins": ["transform-decorators-legacy"]
}
  • Open package.json file and add flow to scripts part:
{
  // ...
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "jest",
    "flow": "flow" // add this
  },
  // ...
}
  • Open Terminal and run npm run flow. You should see No errors! message.
npm run flow

Redux DevTools Extension

This extension allows debugging Redux remotely with Chrome, Firefox, React Native Debugger, etc.

npm install --save-dev redux-devtools-extension

Launch the Simulator

Now, let’s run the app in the simulator.

react-native run-ios;

Making debugging easier

Let’s do a couple of things to simplify debugging and make our lives easier.

Enable hot reloading

Once your app is up and running, press D in the simulator and select Enable Hot Reloading. This will save you some time having to reload the app manually every time you make a change.

Install React Native Debugger

This is a standalone app for debugging React Native apps. It includes React Inspector and Redux DevTools. It makes debugging React Native apps a breeze!

Install with brew on OS X:

brew update && brew cask install react-native-debugger

Prebuilt binaries for other platforms can be found here

To enable debugger, launch it first, press D in the simulator and select Debug JS Remotely.

Breaking design layout down

Let’s take a break from installing stuff and take a look at our design layout.

Before starting coding, we want to break everything down into separate components visually. It helps with code organization and with understanding how everything fits together and which React Native components to use for each part.

Starting coding

  • Create a new folder called src. We’ll be creating all new source files and folders within this folder.

Index files

Next, let’s update our index files. Since we’re going to reuse the same code for both, iOS and Android, we don’t need two different index files. We’ll create App component and reuse it for both.

  • Open index.ios.js file and scrap all of the React Native boilerplate code to start from scratch. Do the same for index.android.js. And add the following code to both of those files.
import { AppRegistry } from 'react-native';
import App from './src/app';

AppRegistry.registerComponent('CoinsChart', () => App);

This code imports App component from src/app.js file and registers it as the main app component.

App component

Now, let’s create our main component. For now, it’ll just render a blank view.

  • Create a new file called app.js within src folder.
import React, { Component } from 'react';
import {
  StyleSheet,
  View
} from 'react-native';

export default class App extends Component {
  render() {
    return (
      <View style={styles.container}>

      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
  flex: 1,        // take up the whole screen
    paddingTop: 20, // put content below status bar
  },
});

Containers

Next, let’s create those three main container components that you can see on the left side of the design layout breakdown. Those are Chart, Date Ranges and Scrollable List.

  • Create a new folder called containers within src folder.

In that folder, we’ll be storing so called containers. They’re different from regular presentational components because they work with Redux storage. They connect to Redux storage, get some data from it, process it if needed and pass the data down to presentational components. Those containers are marked red on the design layout breakdown diagram.

Chart container

  • Create a new file called chart.js within src/containers folder.
import React, { Component } from 'react';
import {
  StyleSheet,
  View
} from 'react-native';

export default class Chart extends Component {

  render() {
    return (
      <View style={styles.container}>

      </View>
    );
  }

}

const styles = StyleSheet.create({
  container: {
    flex: 38, // take 38% of the screen height
    backgroundColor: '#FFFFFF',
  },
});

Ranges container

  • Create a new file called ranges.js within src/containers folder.
import React, { Component } from 'react';
import {
  StyleSheet,
  View
} from 'react-native';

export default class Ranges extends Component {

  render() {
    return (
      <View style={styles.container}>

      </View>
    );
  }

}

const styles = StyleSheet.create({
  container: {
    backgroundColor: '#673AB7',
  },
});

List container

  • Create a new file called list.js within src/containers folder.
import React, { Component } from 'react';
import {
  StyleSheet,
  View
} from 'react-native';

export default class List extends Component {

  render() {
    return (
      <View style={styles.container}>

      </View>
    );
  }

}

const styles = StyleSheet.create({
  container: {
    flex: 62, // take 62% of the screen height
    backgroundColor: '#673AB7',
  },
});

Update App component

Next, let’s update App component to make it render those three containers we just created.

  • Open app.js file within src folder and add highlighted lines.
import React, { Component } from 'react';
import {
  StyleSheet,
  View
} from 'react-native';
import Chart from './containers/chart';
import Ranges from './containers/ranges';
import List from './containers/list';

export default class App extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Chart />
        <Ranges />
        <List />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,        // take up the whole screen
    paddingTop: 20, // put content below status bar
  },
});

Bring up the Simulator to check out the progress. Now we have our three containers with different backgrounds. We’re getting closer. Let’s keep going.

Components

Let’s create a folder for storing regular presentational components. Presentational components are not aware of the Redux storage, API calls, etc. They just render whatever data we pass down. Those are marked orange and blue on the design layout breakdown diagram.

  • Create a new folder called containers within src folder.

Chart component

Next, let’s build a component that renders charts. We want it to take in an array of prices as a prop and render a chart for those prices.

Technically we want to have an area chart in the end. But, we’ll call it a line chart and add a prop called fill that takes in color and fills the area below the line making it an area chart. So it can be either a line chart with transparent filling or an area chart with a solid color filling.

  • Create a new folder called chart within src/components folder. We’ll use it for storing all of the chart related components.
  • Create a new file called line.js within src/components/chart folder. For now, we’re going to have just one chart component that renders a line/area chart.
// @flow

import React, { Component } from 'react';
import {
  Dimensions,
  LayoutAnimation,
  StyleSheet,
  View
} from 'react-native';
import {
  Group,
  Path,
  Surface,
  Shape
} from 'react-native/Libraries/ART/ReactNativeART';

export default class Line extends Component {

  props: {
    values: Array<number>,
    fillColor: string,
    strokeColor: string,
    strokeWidth: number,
  };

  static defaultProps = {
    fillColor: 'rgba(103, 58, 183, 1)',       // solid violet color
    strokeColor: 'rgba(103, 58, 183, 0.25)',  // semi-transparent violet
    strokeWidth: 8,
  };

  state = {
    // set initial width to screen width so when animated it stays constant,
    // try setting it to zero and see what happens on initial load
    width: Dimensions.get('window').width,
    // set initial height to zero so when updated to actual height and
    // animated, the chart raises from the bottom to the top of the container
    height: 0,
  };

  componentWillUpdate() {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
  }

  // Handle container view's onLayout event to get its width and height after rendered and
  // update the state so the component can render the chart using actual width and height
  onLayout = (event: Object) => {
    // pull out width and height out of event.nativeEvent.layout
    const {
      nativeEvent: {
        layout: {
          width,
          height
        }
      }
    } = event;
    // update the state
    this.setState({
      width,
      height,
    })
  };

  buildPath = (values: Array<number>): Path => {
    const {
      strokeWidth,
    } = this.props;
    const {
      width,
      height,
    } = this.state;

    let firstPoint: boolean = true,
      // holds x and y coordinates of the previous point when iterating
      previous: { x: number, y: number };

    const
      minValue = Math.min(...values),
      maxValue = Math.max(...values) - minValue,
      // step between each value point on horizontal (x) axis
      stepX = width / (values.length - 1 || 1),
      // step between each value point on vertical (y) axis
      stepY = maxValue
        ? (height - strokeWidth * 2) / maxValue
        : 0,
      // adjust values so that min value becomes 0 and goes to the bottom edge
      adjustedValues = values.map(value => value - minValue)
    ;

    let path = Path()
    // start from the left bottom corner so we could fill the area with color
      .moveTo(-strokeWidth, strokeWidth);

    adjustedValues.forEach((number, index) => {
      let x = index * stepX,
        y = -number * stepY - strokeWidth;
      if (firstPoint) {
        // straight line to the first point
        path.lineTo(-strokeWidth, y);
      }
      else {
        // make curved line
        path.curveTo(previous.x + stepX / 3, previous.y, x - stepX / 3, y, x, y);
      }
      // save current x and y coordinates for the next point
      previous = { x, y };
      firstPoint = false;
    });

    return path
      // line to the right bottom corner so we could fill the area with color
      .lineTo(width + strokeWidth, strokeWidth)
      .close();
  };

  render() {
    const {
      values,
      fillColor,
      strokeColor,
      strokeWidth
    } = this.props;
    const {
      width,
      height,
    } = this.state;
    return (
      <View
        style={styles.container}
        onLayout={this.onLayout}
      >
        <Surface width={width} height={height}>
          <Group x={0} y={height}>
            <Shape
              d={this.buildPath(values)}
              fill={fillColor}
              stroke={strokeColor}
              strokeWidth={strokeWidth}
            />
          </Group>
        </Surface>
      </View>
    );
  }

}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    // align at the bottom of the container so when animated it rises to the top
    justifyContent: 'flex-end',
  },
});

When this component gets rendered, this.onLayout is getting called and it gets the component’s width and height. We need those to render the chart, and we couldn’t know them in advance because we’re using Flexbox for layout and not absolute values. That’s why we’re using onLayout event of View component.

There’s only one required props values. It expects an array of numbers. Note how we didn’t mark the rest of the props as optional with Flow. That’s because we have default values for those set within defaultProps. Otherwise, we’d mark optional props with a question mark, for instance fillColor?: string.

We’re also using React Native’s LayoutAnimation component to automatically animate layout changes. Initially, component has a height of 0, but after onLayout event gets triggered and updates the height LayoutAnimation animates the height change and the chart rises from the bottom of the container to the top.

And to render the chart itself we’re using a few components of ART library, including Surface to define an area of a certain width and height for the chart, Group that takes all the surface space, and Shape to draw and fill the chart.

Updating Chart container

Ok, let’s see how that chart looks!

  • Open up chart.js file within src/containers folder to make some changes.
  • First of all, we need to import our newly created Line component.
// existing imports
import Line from '../components/chart/line';
  • And second, let’s add Line with some random values inside <View style={styles.container}> of render() just to see how it works.
    <View style={styles.container}>
        <Line values={[40, 30, 70, 60, 100, 70, 40, 70, 50]} />
      </View>

Alright, bring up the simulator and press press R to reload the app.

Wrapping up

And that’s it for the part 1. Check out Part 2 to find out how to set up Redux store, get coin prices through the API and build a list of coins.

Recommended Reading

Spread the Word
  • Pingback: Charts in React Native (Part 2) | Rational App Development()

  • John Snyder

    This is an incredible post. Thanks Konstantin.

  • Flávio Fearn

    Great post! Thanks!

  • Why

    спасибо! хорошо!
    Thank you for the post i learned alot!~

  • Jayden Godsell

    Great article. Can you give me details on the editor being used. I am having a tough time implementing the linking of the ART library. If I am using atom, any other way I can do it?

    Also, can we run this using Expo. Any general guidance will be greatly appreciated

  • darkmoody

    Great tutorial! Definitely learned a lot. It’s super useful to build a chart from scratch if you don’t want to increase the binary size by importing libraries such as react native charts.

  • Lemon Grenade

    I want more like this artwork

  • Rafael Carrenho

  • Kane

    Not sure ! Everyone is saying he is younger