Driving App Engagement With Personalization Techniques

One of the hardest decisions to make when starting a new app is which platforms to target. A mobile app gives you more control and better performance but isn’t as universal as the web. If you’re making a mobile app, can you afford to support both iOS and Android? What about trying to build a mobile app and a responsive web app? Ultimately, the best experience for your customers is for your app to work everywhere, but the development and maintenance costs of that can be prohibitive.

We have already seen how1 React Native can help you make iOS and Android apps with a shared code base, without sacrifices in quality. But what about the web? This is exactly the problem the React Native for Web312 project is trying to solve. Instead of forcing you to maintain two separate code bases for your mobile and web apps, or making a hybrid app, with all its compromises3, React Native for Web is intended to let you write a single app that runs in a browser using standard web technologies, or on iOS and Android as a real native mobile app. While I don’t think the project is ready for production use yet, its potential success could mark a massive change in how large multi-platform applications are built. Let’s jump in!

How It Works Link

You might be thinking, “Wait! doesn’t React already work on the web?” You wouldn’t be wrong. Unfortunately, traditional React and React Native build on a different set of primitives. React uses <div>, <p> and <input>, whereas React Native uses <View>, <Text> and <TextInput>. There are good historical reasons for this, since the building blocks of a web page and of a mobile app are quite different. Nonetheless, it would be great if we could use a single set of shared components.

React Native for Web’s solution is to provide browser-compatible implementations of React Native’s components – meaning, for example, that the <View> of React Native has a DOM-based version that knows how to render to a <div>. While not every React Native component is supported, enough of them are that you could (hopefully) share the majority of your code base.

In addition to the components themselves, styles for React and React Native are written differently. With React, most people use plain CSS or a preprocessor such as Sass4. But in React Native, all styles are written in JavaScript, because there is no DOM and no selectors. With React Native for Web, styles are written just like they would be for React Native, rather than with CSS. This has the benefit of allowing you to write a single set of styles, which will work on both native mobile and the web.

We’ll take a deeper look later at how these ideas work in practice and at how much code is actually reusable. First, let’s get a sample app going.

Starting A New React Native Project Link

To get started, we will need to set up our project. At first, this will just be a regular React Native app, and then we’ll add React Native for Web. If you are following along, you’ll need to complete React Native’s “Getting Started325” guide before heading into the next section.

Once you’ve got React Native installed, you can run the following command from your terminal:

react-native init ReactNativeWeb

This will make a new React Native project named ReactNativeWeb. After it has finished installing, you can cd ReactNativeWeb, and then react-native run-ios or react-native run-android. If everything has gone correctly, you should see a friendly welcome message on your iOS or Android simulator or device.

6

React Native iOS welcome screen (View large version7)
React Native Android welcome screen8

React Native Android welcome screen (View large version9)

Notice that React Native has created two JavaScript files in our project’s directory: index.android.js and index.ios.js. You can edit any of the styles or logic in these files and see those changes update in the running app. As you can probably guess, the .android.js file is for Android, and the .ios.js file is for iOS. Fortunately, separate files are only needed when you want multiple versions of a given file per platform. Most of the time, you’ll have a single file per component.

Managing Dependencies Link

Before we can get our app running in a web browser, we’ll need to get a bit of package installation out of the way. First, run the following to install both the react-native-web package and the official React web packages.

npm i react react-dom react-native-web --save

(You might see some errors about peer dependencies from this command. You should be safe to ignore them, because they didn’t cause me any problems. If newer versions of any of these packages are out when you run the commands, though, you might need to adjust the installed versions.)

At this point, your package.json file should look something like this:

{
  "name": "ReactNativeWeb",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start"
  },
  "dependencies": {
    "react": "15.1.0",
    "react-dom": "15.1.0",
    "react-native": "0.28.0",
    "react-native-web": "0.0.25"
  }
}

While we have what seems to be everything required for our React Native app to run in a web browser, we must take a brief detour to consider the realities of web development. React Native’s packager compiles your ECMAScript 6 code to something that a phone’s JavaScript engine can understand, but it won’t help us in the browser. If we tried to run our app in a web browser right now, it would quickly fail due to syntax errors.

To solve this problem, we will use Babel10 and webpack11. Babel will compile our ECMAScript 6 code into browser-compatible ECMAScript 5, and webpack will bundle the compiled JavaScript, as well as just generally make development faster. (There are other options for this. If you prefer another compiler or bundler, feel free to use it instead.)

Here are the installation commands to run:

npm i webpack babel-loader babel-preset-react babel-preset-es2015 --save
npm i webpack-dev-server --save-dev

Here, babel-loader and webpack-dev-server will be used to bundle and serve our JavaScript, while babel-preset-react and babel-preset-es2015 tell Babel which plugins we need to compile our code.

Here is what your package.json file should look like now:

{
  "name": "ReactNativeWeb",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start"
  },
  "dependencies": {
    "babel-loader": "6.2.4",
    "babel-preset-es2015": "6.9.0",
    "babel-preset-react": "6.5.0",
    "react": "15.1.0",
    "react-dom": "15.1.0",
    "react-native": "0.28.0",
    "react-native-web": "0.0.25",
    "webpack": "1.13.1"
  },
  "devDependencies": {
    "webpack-dev-server": "1.14.1"
  }
}

Configuring Link

Those are all of the packages we will need. But more setup is required before our app will work in a browser.

webpack.config.js Link

First, we’ll make a webpack config file. This file tells webpack how to build, bundle and serve our compiled code. In addition, we are going to use the alias property to automatically replace imports on react-native with react-native-web. This file should be placed in your project’s root.

const webpack = require('webpack');

module.exports = {
  entry: {
    main: './index.web.js',
  },
  module: {
    loaders: [
      {
        test: /.js?$/,
        exclude: /node_modules/,
        loader: 'babel',
        query: {
          presets: ['es2015', 'react'],
        },
      },
    ],
  },
  resolve: {
    alias: {
      'react-native': 'react-native-web',
    },
  },
};

index.html Link

Now, we need to create an HTML file for our app to run in. This will be pretty simple because it will just be a skeleton to attach our React app to.

<!DOCTYPE html>
<html>
<head>
  <title>React Native Web</title>
  <meta charSet="utf-8" />
  <meta content="initial-scale=1,width=device-width" name="viewport" />
</head>
<body>
  <div id="react-app"></div>
  <script type="text/javascript" src="/bundle.js"></script>
</body>
</html>

index.web.js Link

Finally, we must make an index JavaScript file for the web. The contents of this file can be the same as index.ios.js or index.android.js, but with one additional line to attach to the DOM. The div with the ID react-app from our HTML file must be selected and then used in the call to AppRegister.runApplication.

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';

class ReactNativeWeb extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Welcome to React Native!
        </Text>
        <Text style={styles.instructions}>
          To get started, edit index.web.js
        </Text>
        <Text style={styles.instructions}>
          Press Cmd+R to reload
        </Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

AppRegistry.registerComponent('ReactNativeWeb', () => ReactNativeWeb);
AppRegistry.runApplication('ReactNativeWeb', { rootTag: document.getElementById('react-app') });

Now, just run ./node_modules/.bin/webpack-dev-server --inline to start webpack, and open your browser to http://localhost:8080/12. Fingers crossed, you will see a familiar welcome message but in the browser!

React Native web welcome screen13

React Native web welcome screen (View large version14)

With all of that setup complete, we are ready to start tinkering!

Experimenting With The Code Link

Create a FriendsList.js Component Link

Let’s start by making a friends list. This will be a good simple stress test of React Native for Web, because we need to use a few different components for it: <Image>, <Text>, <View> and <ListView>.

import React, { Component } from 'react';
import {
  Image,
  ListView,
  StyleSheet,
  Text,
  View,
} from 'react-native';

const styles = StyleSheet.create({
  list: {
    marginTop: 20,
  },
  friend: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'flex-start',
  },
  avatar: {
    margin: 10,
    width: 50,
    height: 50,
    borderRadius: 25,
  },
  name: {
    fontSize: 18,
    color: '#000',
  }
});

export default class FriendsList extends Component {
  constructor(props) {
    super(props);
    const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
    this.state = {
      ds: ds.cloneWithRows(props.friends),
    };
  }

  render() {
    return (
      <ListView
        dataSource={this.state.ds}
        style={styles.list}
        renderRow={(friend) =>
          <View style={styles.friend}>
            <Image style={styles.avatar} source={{ uri: friend.avatarUrl }} />
            <Text style={styles.name}>{friend.firstName} {friend.lastName}</Text>
          </View>
        } />
    );
  }
}

We’ll need to edit our index files, too, so that a friends array gets passed in as a prop.

import FriendsList from './FriendsList';
import React, { Component } from 'react';
import {
  AppRegistry,
  Text,
  View
} from 'react-native';

const friends = [
  {
    id: 1,
    firstName: 'Jane',
    lastName: 'Miller',
    avatarUrl: 'https://placehold.it/100x100',
  },
  {
    id: 2,
    firstName: 'Kate',
    lastName: 'Smith',
    avatarUrl: 'https://placehold.it/100x100',
  },
  {
    id: 3,
    firstName: 'Kevin',
    lastName: 'Yang',
    avatarUrl: 'https://placehold.it/100x100',
  },
];

class ReactNativeWeb extends Component {
  render() {
    return <FriendsList friends={friends} />;
  }
}

AppRegistry.registerComponent('ReactNativeWeb', () => ReactNativeWeb);

Upon running it in iOS or Android, you should see something like this:

React Native iOS friends list15

React Native iOS friends list (View large version16)

Looks good so far. Let’s see the web version:

React Native web friends list17

React Native web friends list. (View large version18)

Uh oh! Turns out there isn’t any web support yet for ListView‘s DataSource, effectively making ListView completely unusable.

Friend.js Link

We can work around this lack of support for now. Let’s make a Friend component for the individual rows, but have a FriendsList component per platform. This will separate out the shared code that works everywhere but allow us to customize each platform where we need to.

import React, { Component } from 'react';
import {
  Image,
  StyleSheet,
  Text,
  View,
} from 'react-native';

const styles = StyleSheet.create({
  friend: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'flex-start',
  },
  avatar: {
    margin: 10,
    width: 50,
    height: 50,
    borderRadius: 25,
  },
  name: {
    fontSize: 18,
    color: '#000',
  }
});

export default class Friend extends Component {
  render() {
    return (
      <View style={styles.friend}>
        <Image style={styles.avatar} source={{ uri: this.props.avatarUrl }} />
        <Text style={styles.name}>{this.props.firstName} {this.props.lastName}</Text>
      </View>
    );
  }
}

FriendsList.ios.js Link

import Friend from './Friend';
import React, { Component } from 'react';
import {
  Image,
  ListView,
  StyleSheet,
  Text,
  View,
} from 'react-native';

const styles = StyleSheet.create({
  list: {
    marginTop: 20,
  },
});

export default class FriendsList extends Component {
  constructor(props) {
    super(props);
    const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
    this.state = {
      ds: ds.cloneWithRows(props.friends),
    };
  }

  render() {
    return (
      <ListView
        dataSource={this.state.ds}
        style={styles.list}
        renderRow={(friend) =>
          <Friend
            key={friend.id}
            avatarUrl={friend.avatarUrl}
            firstName={friend.firstName}
            lastName={friend.lastName} />
        } />
    );
  }
}

On iOS, our ListView usage code is unchanged. (I’m leaving out the Android code example here and for the rest of the article, for brevity. The Android and iOS code can be the same for the rest of the code samples.)

FriendsList.web.js Link

import Friend from './Friend';
import React, { Component } from 'react';
import {
  Image,
  Text,
  View,
} from 'react-native';

export default class FriendsList extends Component {
  render() {
    return (
      <View>
        {this.props.friends.map(friend =>
          <Friend
            key={friend.id}
            avatarUrl={friend.avatarUrl}
            firstName={friend.firstName}
            lastName={friend.lastName} />
        )}
      </View>
    );
  }
}

Now, for the web, we use the map function to render each Friend, similar to traditional React.

React Native friends list on the web, now working19

React Native friends list on the web, now working (View large version20)

Much better. At this point, hearing that ListView requires workarounds might be enough to make you think that React Native for Web isn’t ready for production use. I am inclined to agree, particularly since lists constitute a large percentage of many applications. How much it matters will vary depending on the project, though. On the bright side, all of our other React Native code so far has been completely reusable. In any case, I am still interested in exploring it further, because there is still much potential in the ideas on display here. Let’s continue with our sample app.

Instead of hardcoding a handful of list items, we can use JSON Generator21 to create a long list for us to work with. If you haven’t used it before, JSON Generator is a great tool for creating dummy and development data. Here is the structure I have defined, which adds a few fields on top of what we already have.

[
  '{{repeat(200)}}',
  {
    id: '{{guid()}}',
    firstName: '{{firstName()}}',
    lastName: '{{surname()}}',
    avatarUrl: 'https://placehold.it/100x100',
    isOnline: '{{bool()}}',
    company: '{{company()}}',
    email: '{{email()}}'
  }
]

And here is a snippet of some of the generated data:

[
  {
    "id": "c5368bbe-adfb-424f-ade3-9d783befa2b6",
    "firstName": "Hahn",
    "lastName": "Rojas",
    "avatarUrl": "https://placehold.it/100x100",
    "isOnline": true,
    "company": "Orbixtar",
    "email": "[email protected]"
  },
  {
    "id": "15ef2834-3ba5-4621-abf1-d771d39c2dd6",
    "firstName": "Helen",
    "lastName": "Stout",
    "avatarUrl": "https://placehold.it/100x100",
    "isOnline": true,
    "company": "Ebidco",
    "email": "[email protected]"
  },
  {
    "id": "1ef05de1-fd8e-41ae-85ac-620b6d716b62",
    "firstName": "Floyd",
    "lastName": "Mcpherson",
    "avatarUrl": "https://placehold.it/100x100",
    "isOnline": false,
    "company": "Ecraze",
    "email": "[email protected]"
  },
  ...
]

To use it, just take your generated JSON and replace our friends array declaration from before. Of course, you can move that data into its own file if you’d like, so that your code files aren’t cluttered with data. In a real application, we would get that data from an API server.

Friend.js Link

Next, we can add these new fields to the Friend component.

...
render() {
  return (
    <View style={styles.friend}>
      <Image
        style={[styles.avatar, { borderColor: this.props.isOnline ? '#9d9' : '#d99' }]}
        source={{ uri: this.props.avatarUrl }} />

      <View>
        <Text style={styles.name}>{this.props.firstName} {this.props.lastName}</Text>
        <Text style={styles.company}>{this.props.company}</Text>
        <Text style={styles.email}>{this.props.email}</Text>
      </View>
    </View>
  );
}
...

FriendsList.js Link

Next, add them as props in each platform’s FriendsList.

...
<Friend
  key={friend.id}
  avatarUrl={friend.avatarUrl}
  firstName={friend.firstName}
  lastName={friend.lastName}
  isOnline={friend.isOnline}
  company={friend.company}
  email={friend.email} />
...
React Native long friends list for iOS22

React Native’s long friends list for iOS (View large version23)
React Native long friend list web24

React Native’s long friends list for the web (View large version25)

So far so good. It is encouraging to see that the core components seem to work well.

Friend.js Link

Next, we can add an animation with a transformation to see how well those work. Let’s make it so that when you tap a row, it animates left and right before returning to its initial position. We will need to add imports for Animated and TouchableOpacity, and wire up the animation and press handler.

import {
  Animated,
  TouchableOpacity,
  ...
} from 'react-native';

...

export default class Friend extends Component {
  constructor(props) {
    super(props);
    this.state = {
      translateValue: new Animated.Value(0),
    };
  }

  animate() {
    Animated.sequence([
      Animated.timing(this.state.translateValue, {
        toValue: 50,
        duration: 200,
      }),
      Animated.timing(this.state.translateValue, {
        toValue: -50,
        duration: 200,
      }),
      Animated.timing(this.state.translateValue, {
        toValue: 0,
        duration: 200,
      })
    ]).start();
  }

  render() {
    return (
      <TouchableOpacity onPress={() => this.animate()} style={[styles.friend, { transform: [{ translateX: this.state.translateValue }]}]}>
        <Image
          style={[styles.avatar, { borderColor: this.props.isOnline ? '#9d9' : '#d99' }]}
          source={{ uri: this.props.avatarUrl }} />

        <View>
          <Text style={styles.name}>{this.props.firstName} {this.props.lastName}</Text>
          <Text style={styles.company}>{this.props.company}</Text>
          <Text style={styles.email}>{this.props.email}</Text>
        </View>
      </TouchableOpacity>
    );
  }
}

Looks good on mobile.

React Native iOS animation26

React Native iOS animation (View large version27)

What about the web?

React Native web animation28

React Native web animation (View large version29)

No luck. Our TouchableOpacity throws an error when pressed. Apparently, this will be fixed30 in the next release and is only present for our particular combination of versions. Attempting to run the animation without using TouchableOpacity causes the same error, too.

I am going to stop here, but if you want to continue on your own, here is a list of topics you could research next:

  • How well do the remaining React Native components and APIs work? We’ve seen that some definitely don’t work, but we don’t yet have a comprehensive list of support.
  • You could explore more extensive styling work, including media queries.
  • React Native for Web supports server rendering. This could be particularly cool because, if it works, it would mean that you could have a single code base driving native mobile applications and a responsive web app that is SEO-optimized.

Conclusion Link

As you can tell, React Native for Web is definitely not ready for production. There are too many unsupported components, even in our small demo app, for me to feel confident about using it in a real project. The most encouraging thing to me, though, is that the pieces that do work seem to completely work, and the parts that don’t, fail entirely. I find that much preferable to the entire thing just kind of working. At the moment, it seems like the project just needs more time to build support. If everything was only 50% functional, I would view that as a sign that the approach is fundamentally broken.

Despite the problems, I still think this is a very exciting project and worth keeping an eye on.

Resources Link

(da, ml, al)

  1. 1 https://www.smashingmagazine.com/2016/04/consider-react-native-mobile-app/
  2. 2 https://github.com/necolas/react-native-web
  3. 3 http://www.apptentive.com/blog/5-points-to-consider-before-making-a-hybrid-mobile-app/
  4. 4 http://sass-lang.com/
  5. 5 https://facebook.github.io/react-native/docs/getting-started.html
  6. 6 https://www.smashingmagazine.com/wp-content/uploads/2016/07/01-react-native-welcome-ios-opt.png
  7. 7 https://www.smashingmagazine.com/wp-content/uploads/2016/07/01-react-native-welcome-ios-opt.png
  8. 8 https://www.smashingmagazine.com/wp-content/uploads/2016/07/02-react-native-welcome-android-opt.png
  9. 9 https://www.smashingmagazine.com/wp-content/uploads/2016/07/02-react-native-welcome-android-opt.png
  10. 10 https://babeljs.io/
  11. 11 https://webpack.github.io/
  12. 12 http://localhost:8080/
  13. 13 https://www.smashingmagazine.com/wp-content/uploads/2016/07/03-react-native-welcome-web-opt.png
  14. 14 https://www.smashingmagazine.com/wp-content/uploads/2016/07/03-react-native-welcome-web-opt.png
  15. 15 https://www.smashingmagazine.com/wp-content/uploads/2016/07/04-react-native-friends-ios-opt.png
  16. 16 https://www.smashingmagazine.com/wp-content/uploads/2016/07/04-react-native-friends-ios-opt.png
  17. 17 https://www.smashingmagazine.com/wp-content/uploads/2016/07/05-react-native-friends-web-opt.png
  18. 18 https://www.smashingmagazine.com/wp-content/uploads/2016/07/05-react-native-friends-web-opt.png
  19. 19 https://www.smashingmagazine.com/wp-content/uploads/2016/07/06-react-native-friends-web-2-opt.png
  20. 20 https://www.smashingmagazine.com/wp-content/uploads/2016/07/06-react-native-friends-web-2-opt.png
  21. 21 http://www.json-generator.com/
  22. 22 https://www.smashingmagazine.com/wp-content/uploads/2016/07/07-react-native-friends-2-ios-opt.png
  23. 23 https://www.smashingmagazine.com/wp-content/uploads/2016/07/07-react-native-friends-2-ios-opt.png
  24. 24 https://www.smashingmagazine.com/wp-content/uploads/2016/07/08-react-native-friends-2-web-opt.png
  25. 25 https://www.smashingmagazine.com/wp-content/uploads/2016/07/08-react-native-friends-2-web-opt.png
  26. 26 https://www.smashingmagazine.com/wp-content/uploads/2016/07/09-react-native-ios-animation-opt.gif
  27. 27 https://www.smashingmagazine.com/wp-content/uploads/2016/07/09-react-native-ios-animation-opt.gif
  28. 28 https://www.smashingmagazine.com/wp-content/uploads/2016/07/10-react-native-web-animation-opt.png
  29. 29 https://www.smashingmagazine.com/wp-content/uploads/2016/07/10-react-native-web-animation-opt.png
  30. 30 https://github.com/necolas/react-native-web/issues/150
  31. 31 https://github.com/necolas/react-native-web
  32. 32 https://facebook.github.io/react-native/docs/getting-started.html

Powered by WPeMatico

28 Comments

  1. Dim August 8, 2016
  2. peter August 8, 2016
  3. MF Simchock August 8, 2016
  4. Paul d'Aoust August 8, 2016
  5. Alex Dimitrov August 8, 2016
  6. Paul Francis August 8, 2016
  7. julian August 8, 2016
  8. Rex August 9, 2016
  9. Luigi Maselli August 9, 2016
  10. Jon Loldrup August 11, 2016
  11. James Gillmore August 12, 2016
  12. James Gillmore August 12, 2016
  13. Dominik Weidenfeld August 12, 2016
  14. Jan August 12, 2016
  15. yiminghe August 12, 2016
  16. Jonas August 12, 2016
  17. Stardrive August 13, 2016
  18. Sam S Lee August 16, 2016
  19. Rolando Barbella August 23, 2016
  20. Kyle August 24, 2016
  21. Yash Vekaria September 2, 2016
  22. ITS-PAT September 6, 2016
  23. Bobby September 18, 2016
  24. Bobby September 18, 2016
  25. Shishir Choudhary September 21, 2016
  26. JoVdB October 4, 2016
  27. Andy Piddock October 5, 2016
  28. Jafar November 5, 2016

Leave a Reply