Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Crossing platforms with JavaScript & React

Crossing platforms with JavaScript & React

53f03faec1f8e5de88b555cb02ae6ac9?s=128

Robert DeLuca

February 07, 2017
Tweet

Transcript

  1. Crossing platforms with JavaScript & React

  2. @robdel12

  3. JavaScript is EVERYWHERE

  4. Web

  5. Server

  6. iOS

  7. Android

  8. Desktop

  9. None
  10. What is cross platform JS?

  11. JS that can run on more than one platform

  12. None
  13. “Why did the iOS team implement it like this?”

  14. “The Android app currently doesn’t support that”

  15. https://twitter.com/dan_abramov/status/812047645732651009

  16. Easier to share code across many teams

  17. More team collaboration since there’s more overlap

  18. It allows teams to own products & not be separated

    by technology
  19. TL;DR your team now owns the iOS, Android, and (maybe)

    web apps.
  20. Consistency is

  21. Cheaper

  22. If you can build iOS & Android apps in the

    same code base it should be cheaper
  23. Why not bet on the web?

  24. Native will be better than mobile web for a while

  25. Why not take the web tooling & get native results?

  26. You are betting on the web

  27. Can’t beat them, join them

  28. I decided to be ambitious

  29. Build an Instagram clone for Web, iOS, & Android

  30. Why an Instagram clone?

  31. Use Impagination.js to power an Infinite scroll of images

  32. None
  33. Impagination will work on any JS codebase

  34. Building infinite scroll in React Native with Impagination http://bit.ly/reactnativeinfinitescroll

  35. We’ve already used Impagination in four different platforms

  36. What else can be shared?

  37. Experiment time

  38. None
  39. Three phases to the experiment • Planning • Implementation •

    Postmortem
  40. Planning

  41. None
  42. The stack • React Native • React (DOM) • Auth0

    (authentication) • Graph.cool (backend) • Impagination (infinite datasets)
  43. What should the app do? • Login / Sign up

    • See your profile & images you’ve posted • Edit your profile • Post a new photo • Main list feed showing everyones posts
  44. Web demo

  45. Implementation

  46. What’s the approach?

  47. Build the web app

  48. Start to build the native app

  49. Realize I’ve already solved these problems in the web app

  50. Refactor

  51. ListPage.js

  52. ListPage.js handles both UI & data right now

  53. ListPage for native duplicates a lot form web ListPage

  54. class ListPage extends React.Component { static propTypes = { data:

    React.PropTypes.object, } state = { dataset: null, datasetState: null, } setupImpagination() {} componentWillMount() {this.setupImpagination();} setCurrentReadOffset = (event) => {} render () { return ( <div style={{maxWidth: "600px", margin: "0 auto", padding: "20px 0"}}> <Infinite elementHeight={ITEM_HEIGHT} handleScroll={this.setCurrentReadOffset} useWindowAsScrollContainer> {this.state.datasetState.map(record => { if (record.isPending && !record.isSettled) { return <LoadingPost key={Math.random()} />; } return <Photo key={record.content.id} photo={record.content} user={record.content.user} />; })} </Infinite> </div> ); } } const FeedQuery = gql`query($skip: Int!, $first: Int!) { allPhotos(orderBy: createdAt_DESC, first: $first, skip: $skip) { } }`; export default graphql(FeedQuery, {options: {variables: { skip: 0, first: PAGE_SIZE }}})(ListPage); Web ListPage.js
  55. ` class ListPage extends React.Component { static propTypes = {

    data: React.PropTypes.object, } state = { dataset: null, datasetState: null, } setupImpagination() {} componentWillMount() {this.setupImpagination();} setCurrentReadOffset = (event) => {} render () { return ( <ScrollView style={{flex: 1}} scrollEventThrottle={300} onScroll={this.setCurrentReadOffset} removeClippedSubviews={true}> {this.state.datasetState.map(record => { if(record.isPending && !record.isSettled) { return <LoadingPost key={Math.random()}/>; } return <Photo key={record.content.id} photo={record.content} user={record.content.user} />; })} </ScrollView> ); } } const FeedQuery = gql`query($skip: Int!, $first: Int!) { allPhotos(orderBy: createdAt_DESC, first: $first, skip: $skip) { } }`; export default graphql(FeedQuery, {options: {variables: { skip: 0, first: PAGE_SIZE }}})(ListPage); Native ListPage.js
  56. Everything but the UI is the same

  57. New structure

  58. Presentation & container components

  59. <IndexRoute component={ListPageContainer} /> <Route path='feed' component={ListPageContainer} />

  60. import ListPageView from ‘../components/presentational/ListPageView’; class ListPageContainer extends React.Component { state

    = { dataset: null, datasetState: null, } setupImpagination() {} componentWillMount() {this.setupImpagination();} setCurrentReadOffset = (event) => {} render () { return ( <ListPageView setCurrentReadOffset={this.setCurrentReadOffset} datasetState={this.state.datasetState} />; ); } } const FeedQuery = gql`query($skip: Int!, $first: Int!) { allPhotos(orderBy: createdAt_DESC, first: $first, skip: $skip) { } }`; export default graphql(FeedQuery, {options: {variables: { skip: 0, first: PAGE_SIZE }}})(ListPage);
  61. Make the container component render a separate presentation component

  62. Leave setting the readOffset to the presentation components

  63. setCurrentReadOffset function is passed as a prop from the container

    component
  64. t import React, { Component } from 'react'; import Infinite

    from 'react-infinite'; import Photo from '../presentational/Photo'; import LoadingPost from '../presentational/LoadingPost'; const ITEM_HEIGHT = 600; const HEADER_HEIGHT = 80; class ListPageView extends Component { setCurrentReadOffset = (event) => { let currentItemIndex = Math.ceil((window.scrollY - HEADER_HEIGHT) / ITEM_HEIGHT); this.props.setCurrentReadOffset(currentItemIndex); } render() { return ( <div style={{maxWidth: "600px", margin: "0 auto", padding: "20px 0"}}> <Infinite elementHeight={ITEM_HEIGHT} handleScroll={this.setCurrentReadOffset} useWindowAsScrollContainer> {this.props.datasetState.map(record => { if (record.isPending && !record.isSettled) { return <LoadingPost key={Math.random()} />; } return <Photo key={record.content.id} photo={record.content} user={record.content.user} />; })} </Infinite> </div> ); } } export default ListPageView; Web presentation component
  65. Native presentation component import React, { Component } from 'react';

    import Photo from '../presentational/Photo'; import LoadingPost from '../presentational/LoadingPost'; import { ScrollView } from 'react-native'; const ITEM_HEIGHT = 485; class ListPageView extends Component { setCurrentReadOffset = (event) => { let currentOffset = Math.floor(event.nativeEvent.contentOffset.y); let currentItemIndex = Math.ceil(currentOffset / ITEM_HEIGHT); this.props.setCurrentReadOffset(currentItemIndex); } render() { return ( <ScrollView style={{flex: 1}} scrollEventThrottle={300} onScroll={this.setCurrentReadOffset} removeClippedSubviews={true}> {this.props.datasetState.map(record => { if(record.isPending && !record.isSettled) { return <LoadingPost key={Math.random()}/>; } return <Photo key={record.content.id} photo={record.content} user={record.content.user} />; })} </ScrollView> ); } } export default ListPageView;
  66. This theme continues throughout the entire app

  67. <IndexRoute component={ListPageContainer} /> <Route path='feed' component={ListPageContainer} /> <Route path='new' component={CreatePostContainer}

    onEnter={this.requireAuth.bind(this)} /> <Route path='signup' component={CreateUserContainer} /> <Route path='profile' component={UserProfileContainer} > <IndexRoute component={UserProfileContainer} /> <Route path='edit' component={EditProfileContainer} /> </Route> <Route path='logout' component={() => <Logout logout={this.handleToken.bind(this)} /> } />
  68. Native app demo

  69. Postmortem

  70. Building the apps in time was hard…

  71. None
  72. None
  73. Figuring out what code is shareable

  74. Figuring out how to make that code shareable

  75. React Router is neat & works cross platform There are

    different imports for React Native & React
  76. None
  77. Auth0 was very easy to implement on both platforms. There

    are different APIs for React Native & React
  78. AsyncStorage vs localStorage

  79. What all ended up being shared?

  80. ✅ List feed ✅ User profile ✅ Edit user profile

    ✅ Sign up ✅ New post
  81. Beyond login mostly everything else is the same

  82. The UI changed but not the business logic

  83. Key takeaways

  84. We’re in a post DOM world

  85. Write JavaScript interaction models

  86. The UI framework will change but the underlying model driving

    it won’t
  87. “It’s just JavaScript”

  88. We get stronger libraries by increasing the number of users

    & contributors.
  89. React makes this very easy thanks to React & React

    Native
  90. I was able to share the same five container components

    across three different platforms
  91. Write one container component and many UI components

  92. The core of this app is shared

  93. That’s a cost savings

  94. I own this entire product & its 3 platforms

  95. In 2 weeks I was able do all of this

  96. Cross platform JS FTW

  97. Instagram also agrees with me https://engineering.instagram.com/react-native-at-instagram- dd828a9a90c7#.i364vchox

  98. None
  99. If this kind of stuff interests you

  100. We’re hiring!

  101. Thanks! @robdel12