[React Native] Using variable as JSON’s key instead of hardcoded string

tl;dr

A relatively short blogpost sharing why I use variable as JSON’s key in React Native.

Introduction

As we all know that, JSON is key-value paired. See below code.

// A JSON
const json = {
  name: "Yik Kok",
  website: "www.yikkok.com"
}

Well, inside the curly brace, left hand side is the key and right hand side is the value.

Is that possible to replace the key with variable ? Yes, it is possible.

** But why ? (Well, I found it’s quite useful, I will explain my thought later)

Let see the code below.

const NAME = "name";
const WEBSITE = "website";

const json = {
  [NAME]: "Yik Kok",
  [WEBSITE]: "www.yikkok.com"
}

We can do it this way when using variable as the JSON’s key

So, what is it gonna do with React Native?

Well, this is because recently I’m figuring how to directly access specific styling from props in React Native – not by the style props, I know this could be done easily with just extend the props.

**(Yes, there’s tradeoff for this. So, I will limited the usage and apply to wherever make sense.)

Before I continue, let have a look for the demo video first.

It’s an ordinary updating text styling when the specific button was clicked. Under the scene, there is no if...else, no ? : (ternary statement) involved.

Let’s see the code.

import React, {useState} from 'react';
import {StyleSheet, Text, View, Button, SafeAreaView} from 'react-native';

const STATUS_NORMAL = 'Normal';
const STATUS_WARNING = 'Warning';
const STATUS_ERROR = 'Error';

const statusDescription = {
  [STATUS_NORMAL]: 'All Green.',
  [STATUS_WARNING]: 'Beware of Warning!',
  [STATUS_ERROR]: 'Error, Error!!!',
};
const App: () => React$Node = props => {
  const [status, setStatus] = useState(STATUS_NORMAL);

  function handleOnChangeStatus(newStatus) {
    return () => {
      setStatus(newStatus);
    };
  }

  return (
    <SafeAreaView style={styles.container}>
      <Text style={styles[status]}>{statusDescription[status]}</Text>

      <View>
        <Button
          title={STATUS_NORMAL}
          onPress={handleOnChangeStatus(STATUS_NORMAL)}
        />
        <Button
          title={STATUS_WARNING}
          onPress={handleOnChangeStatus(STATUS_WARNING)}
        />
        <Button
          title={STATUS_ERROR}
          onPress={handleOnChangeStatus(STATUS_ERROR)}
        />
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'space-around',
    padding: 20,
  },
  [STATUS_NORMAL]: {
    color: 'green',
  },
  [STATUS_WARNING]: {
    color: 'yellow',
    backgroundColor: 'black',
  },
  [STATUS_ERROR]: {
    color: 'red',
  },
});

export default App;

As we can see from the code. There is no if...else clause and no hardcoded string in the component tree. (I’m not saying that we should eliminate all hardcoded string from component tree. In some cases, it’s easy if you hardcoded it instead of using a variable)

Let’s get back to the code. Firstly, I have three variable indicating the status.

const STATUS_NORMAL = 'Normal';
const STATUS_WARNING = 'Warning';
const STATUS_ERROR = 'Error';

Then, I have two JSON – one is the statusDescription and another one is the styles that created using React Native’s StyleSheet API.

const statusDescription = {
  [STATUS_NORMAL]: 'All Green.',
  [STATUS_WARNING]: 'Beware of Warning!',
  [STATUS_ERROR]: 'Error, Error!!!',
};

and

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'space-around',
    padding: 20,
  },
  [STATUS_NORMAL]: {
    color: 'green',
  },
  [STATUS_WARNING]: {
    color: 'yellow',
    backgroundColor: 'black',
  },
  [STATUS_ERROR]: {
    color: 'red',
  },
});

Now, the function – a closure that will pass the variables for update new status.

function handleOnChangeStatus(newStatus) {
  return () => {
    setStatus(newStatus);
  };
}

// e.g
<Button
  title={STATUS_NORMAL}
  onPress={handleOnChangeStatus(STATUS_NORMAL)}
/>

So, now whenever the status get updated, The content will updated accordingly to the references in JSON. For styling as well.

But why ?

Why put it in variable instead of string as the JSON key since both of them have the same purpose ? Hmmm, there is no specific rules to do it this way.

I found them useful to me is because of following reasons:

  1. Eslint plugins help me detect undefined variable but not string or JSON’s key.
    • Which allow me to do early fails detection.
  2. I’m a human, who will make mistake in my life, including typo… most of the time, this gonna take me a lot of time to debug or trace it.
  3. I heavy rely on the IntelliSense feature provided by Editor/IDE where they show me which variable match my need but not string.
  4. I’m lack of the ability to give everything a name and still able to recall them correctly, so put it in variable can help me prevent the duplication key issue.

Just some thought

This is inspire by a recent engineering blogpost from facebook. Where they utilise the SQLite to driven their UI. So, in my free time, I will try out experiment whether it could be done with the combination of JSON, variables and database to render UI in React Native.

Thank you for reading.  😁

[Babel] Module resolver

tl;dr

A blogpost that demonstrate how to use babel-plugin-module-resolver ? How babel-plugin-module-resolver help developers write better file path ?

Outline

  1. Problems
  2. Install and configure babel-plugin-module-resolver
  3. Configure jsconfig.json/tsconfig.json(TypeScript Project)
  4. Configure .flowconfig (for flow-type project)
  5. File path IntelliSense extension in VS Code

#1 Problems

In my React Native project, I used to import the component as below,

import SomeComponent from "../../../SomeComponent" 

Using the ../ – dot notation or relative file path to access the component that I put in another folder. If having a large amount of folder or deeply nested folder structure.

It make the code hard to read and most of the time, I can’t recalled where is that component belong to.

So, babel-plugin-module-resolver come to rescue. With babel-plugin-module-resolver , I can write better file path for my React Native project as below:

import SomeComponent from "@shared/SomeComponent";

or

import HomeScreen from "@screens/HomeScreen";

This is the magic power from babel-plugin-module-resolver.

#2 Installation and configuration

Using npm,

npm install --save-dev babel-plugin-module-resolver

or using yarn,

yarn add --dev babel-plugin-module-resolver

Add below configuration to babel config file – .babelrc or babel.config.js

...
plugins: [
  [
     'module-resolver',
     {
       root: ['./],
       alias: {
         '@screens': './src/screens',
         '@shared': './src/shared'
       }
     }
  ]
]

Now, we can import the Homeor Body component by using below syntax

import Home from "@screens/Home";

or 

import Body from "@shared/Body";

#3 Configure jsconfig/tsconfig

Next, configure the jsconfig.json or tsconfig.json file. If not exist, you can create one. This is to enable the IntelliSense for the editor (At least for VS Code)

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@screens/*": ["./src/screens/*"],
      "@shared/*": ["./src/shared/*"]
    }
  }
}

This way we can tell the editor which directory the editor should look into. So that we can have the editor to look for the definition for us.

** tsconfig.json if your project is TypeScript based.

#4 Configure .flowconfig

Using flow-type in your JavaScript ? Chances are you will get warning, such as Cannot resolve module .

Add following configuration into your .flowconfig

module.name_mapper='^@screens/\(.*\)$' -> '<PROJECT_ROOT>/src/screens/\1'
module.name_mapper='^@shared/\(.*\)$' -> '<PROJECT_ROOT>/src/shared/\1'

module.name_mapper=pattern -> mapped directory

Pattern – Regex or specific path pattern.
Mapped directory – The directory flow-type will look for whenever met the pattern we specified

#5 File path intellisense extension (VS Code)

Before configure the babel module resolver, I’m using an extension – Path Intellisense which can help me autocomplete the filename. It’s an useful extension that greatly improve my productivity.

Take away

  1. Using babel-plugin-module-resolver to resolve messy file path
  2. Configure jsconfig.json or tsconfig.json for intelliSense (autocomplete feature)
  3. Configure .flowconfig for flow-type to resolve module correctly

Thanks for reading and have a nice day. 😁

Feel free to let me know, if this article is useful.

React Navigation 5.0.0

React Navigation Version 5.0

tl;dr

Updated on 18 March 2020

Giving a try on React Navigation version 5.0 – declarative way to implement navigation in React Native.

Let’s start

#1 Prerequisites

@ Let’s start

Generate a new project using react-native cli.

$ react-native init TryReactNavigationV5

or

$ react-native init TryReactNavigationV5 --version [version-number]

@ Install React Navigation

** yarn or npm, depend on your project package management.

Install react-navigation using following command. Take note that now react-navigation use the new package-name.

$ yarn add @react-navigation/native
$ yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view

@ Install React Navigation Stack

Starting React Navigation version 4. The StackNavigator, DrawerNavigator BottomTabNavigator or etc has been isolated as standalone package. Thus, in order to use specific Navigator, one must install from npm.

  • createStackNavigator
$ yarn add @react-navigation/stack
  • createDrawerNavigator
$ yarn add @react-navigation/drawer
  • createBottomTabNavigator
$ yarn add @react-navigation/bottom-tabs
  • createMaterialBottomTabNavigator
$ yarn add @react-navigation/material-bottom-tabs react-native-paper react-native-vector-icons

react-native-vector-icons installation guide.

  • createMaterialTopTabNavigator
$ yarn add @react-navigation/material-top-tabs react-native-tab-view
  • iOS (Installed require library using Cocoapods)
cd ios && pod install && cd ..

To complete the installation of react-native-gesture-handler.

Import react-native-gesture-handler in the entry file – App.js or index.js depend on your file structure. The first line of file before anything else.

import 'react-native-gesture-handler';

...

@ Error from react-native-reanimated

You may probably hitting similar error, after installing the react-native-reanimated and try to run react-native run-android.

This is due to react-native-reanimated library required an extra properties – supportLibVersion in projectRootFolder/android/build.gradle.

Solution,

ext {
    buildToolsVersion = "28.0.3"
    minSdkVersion = 16
    compileSdkVersion = 28
    targetSdkVersion = 28
    supportLibVersion = "28.0.0" // Add this line to resolve the error
}

@ React Navigation – declarative way

// App.js
import React, { useState } from "react";
import {
  View,
  TextInput,
  Text,
  Button,
  StyleSheet,
  ActivityIndicator,
} from "react-native";
import { NavigationProp } from "@react-navigation/core";
import { NavigationNativeContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";

const Stack = createStackNavigator();

const App: () => React$Node = () => {
  const [token, setToken] = useState("");
  const [loading, setLoading] = useState(false);

  if (loading) {
    return (
      <View style={style.container}>
        <ActivityIndicator size="large" />
      </View>
    );
  }

  return (
    <NavigationNativeContainer>
      <Stack.Navigator initialRouteName={token ? "Home" : "Login"}>
        <Stack.Screen
          name="Login"
          component={Login}
          options={{ header: null }}
        />
        <Stack.Screen name="Home" component={Home} />
      </Stack.Navigator>
    </NavigationNativeContainer>
  );
};

const style = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  inputContainer: {
    width: "80%",
    marginBottom: 20,
  },
  input: {
    borderStyle: "solid",
    borderWidth: 1,
  },
  label: {
    fontWeight: "bold",
    paddingVertical: 10,
  },
});

export default App;

<Stack.Navigator /> and <Stack.Screen /> The new declarative way to implement navigation in React Native.

if (loading) {
  return (
    <View style={style.container}>
      <ActivityIndicator size="large" />
    </View>
  );
}

Above code is a loading screen. In the mean while, getting the token from AsyncStorage.

Has error due to missing of Login and Home component ? Now, we will resolve the error. Add the Login and Home screen component respectively.

// In App.js too
import AsyncStorage from "@react-native-community/async-storage";

type Props = {
  navigation: NavigationProp,
};
function Login(props: Props) {
  return (
    <View style={style.container}>
      <View style={style.inputContainer}>
        <Text style={style.label}>Username</Text>
        <TextInput placeholder="Username" style={style.input} />
      </View>

      <View style={style.inputContainer}>
        <Text style={style.label}>Password</Text>
        <TextInput placeholder="******" secureTextEntry style={style.input} />
      </View>
      <Button
        title="Login"
        onPress={async () => {
          const { navigation } = props;

          // testing purpose
          await AsyncStorage.setItem("token", "yourtoken");
          navigation.navigate("Home");
        }}
      />
    </View>
  );
}

function Home() {
  return (
    <View style={style.container}>
      <Text>Home Page</Text>
    </View>
  );
}

Install AsyncStorage from react-native-community/async-storage.

Then, some modification to App component as below.

// App.js

const App: () => $ReactNode = () => {
  // code
  
  useEffect(() => {
    async function init() {
      setLoading(true);
      const userToken = await AsyncStorage.getItem("token");

      if (userToken) {
        setToken(userToken);
      }

      setLoading(false);
    }

    init();
  }, []);

  // return
  return (
    // Code
  )
}

Using the React Hooks – useEffect(). As initialize step, to fetch the token from AsyncStorage.

If the token exists or valid, depend on architecture. Then, we can decide the initialRouteName, either to Home or Login.

<Stack.Navigator initialRouteName={token ? "Home" : "Login"}>

Thanks for reading.  😁

Here is the full gists.

Bookmark action in React Native using FlatList

Bookmark action in React Native Using FlatList

tl;dr

Demonstrate fetching data(500 items) and put into a FlatList and bookmark selected items.

Outline

  1. Displaying a list of items and fetch the source from HackerNews API
  2. Component that implement shouldComponentUpdate
  3. Function to handling bookmark action (Saving the selected items)
  4. Enhance bookmark action using Set from ES6

What we use in this tutorial

  1. React Native Element
  2. PureComponent (For FlatList optimization)
  3. React Lifecycle (componentDidMount, componentDidUpdate and shouldComponentUpdate)
  4. Set in ES6

Fetching data source and display in FlatList

First, let’s create file called StoryList.js and a simple class component that extends PureComponent to optimize <FlatList />.

import React, { PureComponent } from "react";

// Flow-type
type Props = {};

type State = {
  stories: Array<number>,
  bookmarks: Array<number>,
  selectedIndex: number,
};

// For ButtonGroups
const storyTypes = [
  { name: "New", path: "newstories.json" },
  { name: "Top", path: "topstories.json" },
  { name: "Best", path: "beststories.json" },
];

// Default story type
const NEW_STORIES = 0;
class StoryList extends PureComponent<Props, State> {
  constructor(props: Props) {
    super(props);

    // Initialize default value for state
    // Separate data source and bookmarks, instead modify the data source
    this.state = {
      stories: [],
      bookmarks: [],
      selectedIndex: NEW_STORIES,
    };
  }

  render() {
    const { bookmarks, stories, selectedIndex } = this.state;

    // render our view, now it return null
    return null;
  }
}

export default StoryList;

Now, we will implement the React lifecycle – componentDidMount. Before going deeper, let has a function to handle the data fetching first.

fetchStories()

  fetchStories = async (storyType: string) => {
    try {
      const response = await fetch(
        `https://hacker-news.firebaseio.com/v0/${storyType}?print=pretty`,
      ).then(res => res.json());

      if (response) {
        this.setState({
          stories: [...response],
        });
      }
    } catch (error) {
      // handling error
    }
  };

componentDidMount()

  // When a component mounted, it will execute this lifecycle for once
  componentDidMount() {
    const { selectedIndex } = this.state;
    this.fetchStories(storyTypes[selectedIndex].path);
  }

Import the following from respectively library.

import { FlatList } from "react-native";
import { ButtonGroup, ListItem, Icon } from "react-native-elements";

render() – render the UI. Modify the render() method inside the class to the following. this.renderNews has error ? No worry will implement it next.

  render() {
    const { bookmarks, stories, selectedIndex } = this.state;

    return (
      <>
        <ButtonGroup
          buttons={storyTypes.map(storyType => storyType.name)}
          selectedIndex={selectedIndex}
          onPress={(index: number) => {
            this.setState({
              selectedIndex: index,
            });
          }}
        />
        {bookmarks.length > 0 ? (
          <ListItem
            title="Bookmarks"
            rightIcon={<Icon name="keyboard-arrow-right" />}
            containerStyle={{
              padding: 10,
              marginRight: 15,
              marginLeft: 15,
              backgroundColor: "rgba(200, 200, 200, 0.1)",
            }}
            badge={{ value: bookmarks.length }}
          />
        ) : null}
        <FlatList
          data={stories}
          renderItem={this.renderNews}
          keyExtractor={(item, index) => `list-${index}-${item}`}
          initialNumToRender={10}
          extraData={bookmarks} // this is needed to trigger re-rendering
        />
      </>
    );
  }

renderNews()

  renderNews = ({ item }: { item: number }) => {
    const { bookmarks } = this.state;
    return (
      <NewsItem
        id={item}
        // We will find the id that being bookmark, through below method
        isSelected={!!bookmarks?.find((bookmark: number) => bookmark === item)}
        onPressBookmark={this.handleOnPressBookmark(item)}
        onPressStory={details => {
          console.log(details);
        }}
      />
    );
  };

NewsItem – Implement shouldComponentUpdate

Create a new file called NewsItem and a class component.

import React, { Component } from "react";
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
import { Card, ListItem, Icon } from "react-native-elements";
import moment from "moment";

const style = StyleSheet.create({
  cardHeader: {
    alignItems: "center",
    justifyContent: "space-between",
    flexDirection: "row",
    paddingBottom: 10,
  },
});

type Props = {
  id: number,
  isSelected: boolean,
  onPressBookmark: () => void,
  onPressStory: (details: Object) => void,
};

type State = {
  details: Object,
};
class NewsItem extends Component<Props, State> {
  constructor(props: Props) {
    super(props);

    this.state = {
      details: {},
    };
  }

  componentDidMount() {
    const { id } = this.props;
    this.fetchStoryDetails(id);
  }

  shouldComponentUpdate(prevProps: Props, prevState: State) {
    const { isSelected } = this.props;
    const { details } = this.state;

    return details !== prevState.details || isSelected !== prevProps.isSelected;
  }

  fetchStoryDetails = async (id: number) => {
    try {
      const response = await fetch(
        `https://hacker-news.firebaseio.com/v0/item/${id}.json?print=pretty`,
      ).then(res => res.json());

      if (response) {
        this.setState({ details: { ...response } });
      }
    } catch (error) {
      // handling error
    }
  };

  handleOnPressBookmark = () => {
    const { onPressBookmark } = this.props;
    if (onPressBookmark) {
      onPressBookmark();
    }
  };

  handleOnPressStory = () => {
    const { onPressStory } = this.props;
    const { details } = this.state;

    if (onPressStory) {
      onPressStory(details);
    }
  };

  render() {
    const { details } = this.state;
    const { isSelected } = this.props;
    return (
      <Card>
        <View style={style.cardHeader}>
          <Text>{`${moment.unix(details.time).format("DD MMM YYYY")}`}</Text>
          {isSelected ? (
            <Icon name="bookmark" onPress={this.handleOnPressBookmark} />
          ) : (
            <Icon name="bookmark-border" onPress={this.handleOnPressBookmark} />
          )}
        </View>
        <TouchableOpacity onPress={this.handleOnPressStory}>
          <ListItem
            topDivider
            title={details.title}
            subtitle={`By ${details.by}`}
          />
        </TouchableOpacity>
      </Card>
    );
  }
}

export default NewsItem;

Take a closer look at the shouldComponentUpdate. shouldComponentUpdate determines whether a component should re-render or not.

  // By default this lifecycle will return true
  shouldComponentUpdate(prevProps: Props, prevState: State) {
    const { isSelected } = this.props;
    const { details } = this.state;

    // Now, we restrict the condition to only re-render
    // when the following value had changed
    return details !== prevState.details || isSelected !== prevProps.isSelected;
  }

Implement function for bookmark action

Next, go back to StoryList.js file. Then, implement the function for the bookmark action.

// import the NewsItem component
import NewsItem from "NewsItem";

class StoryList extends PureComponent {
  ...

  handleOnPressBookmark = (id: number) => {
    return () => {
       const { bookmarks } = this.state;
       const tmpBookmarks = [...bookmarks]; // clone the array

       // Checking whether the selected "id" exist in the bookmarks list
       if (tmpBookmarks.find((tmpBookmark: number) => tmpBookmark === id)) {
         // Remove the selected "id" if it existed
         const removeBookmark = tmpBookmarks.filter(
           (tmpBookmark: number) => tmpBookmark !== id,
         );

         // update state
         this.setState({
           bookmarks: [...removeBookmark],
         });
       } else {
         // otherwise, push it to the bookmarks list
         tmpBookmarks.push(id);

         this.setState({
           bookmarks: [...tmpBookmarks],
         });
       }
    };
  };
  
  ...
}

Below is the sample. For this demo. (Forget about the SearchBar, is not included in this tutorial)

Enhance the bookmark action by using Set in ES 6

Now, let us refactor the handleOnPressBookmark() as below.

  constructor() {
     this.state = {
       ...,
       bookmarks: new Set([]) // initialize to Set instead of Array
     }
  }

  handleOnPressBookmark = (id: number) => {
    return () => {
      const { bookmarks } = this.state;
      const tmpBookmarks = new Set(bookmarks);

      // Now, the code is easier to read and understand
      // And we save the system resource from looping array
      // Checking whether has the "id"
      if (tmpBookmarks.has(id)) {
        tmpBookmarks.delete(id); // remove it, if existed in the bookmarks
      } else {
        tmpBookmarks.add(id); // otherwise, add into the bookmarks
      }

      this.setState({
        bookmarks: tmpBookmarks,
      });

      // Below is previous version, for easier comparison
      // if (tmpBookmarks.find((tmpBookmark: number) => tmpBookmark === id)) {
      //   const removeBookmark = tmpBookmarks.filter(
      //     (tmpBookmark: number) => tmpBookmark !== id,
      //   );

      //   this.setState({
      //     bookmarks: [...removeBookmark],
      //   });
      // } else {
      //   tmpBookmarks.push(id);

      //   this.setState({
      //     bookmarks: [...tmpBookmarks],
      //   });
      // }
    };
  };

Access bookmarks.size is similar to Array().length

<ListItem
  title="Bookmarks"
  rightIcon={<Icon name="keyboard-arrow-right" />}
  containerStyle={{
    padding: 10,
    marginRight: 15,
    marginLeft: 15,
    backgroundColor: "rgba(200, 200, 200, 0.1)",
  }}
  badge={{ value: bookmarks.size }}
/>

Refactor renderNews() too

  renderNews = ({ item }: { item: number }) => {
    const { bookmarks } = this.state;
    return (
      <NewsItem
        id={item}
        // isSelected={!!bookmarks?.find((bookmark: number) => bookmark === item)}
        // refactor to below format
        isSelected={bookmarks.has(item)}
        onPressBookmark={this.handleOnPressBookmark(item)}
        onPressStory={details => {
          console.log(details);
        }}
      />
    );
  };

Bonus

Implement data fetching in componentDidUpdate. This is related to the onPress function in ButtonGroup. The onPress function only update selectedIndex without further action.

  componentDidUpdate(prevProps: Props, prevState: State) {
    const { selectedIndex } = this.state;

    // Then, we can handle the action in componentDidUpdate
    // Whenever a component re-render, the lifecycle will triggered
    // Thus, we can handle some action here after re-render
    // **Always do comparison, to prevent infinite loop
    if (prevState.selectedIndex !== selectedIndex) {
      this.fetchStories(storyTypes[selectedIndex].path);
    }
  }

Takeaway

  • Utilize React lifecycle – componentDidMount, shouldComponentUpdate, componentDidUpdate
  • The idea to implement a bookmark or similar item selection using FlatList in React Native
  • Using Set to replace array

Thanks for reading. Have a nice day.

Getting Started In React Native

Getting Started in React Native

tl;dr

Some personal experiences on React Native stack. The things that a developer should know. (Not need to know all at once, just for reference, where to go next)

Outline

  1. Tools
  2. Library
  3. Idea

#Tools

  • Programming Language

Mainly in JavaScript – ES6. Here, a resource for learning ES6 feature. Since, React Native is able to expose native iOS and Andriod functionality to JavaScript. In some case, developer should know the programming language in both platform, such as Objective-C or Swift and Java or Kotlin. For example, some localize payment gateway due to the provider only release native SDK. Then, developers have to create the “bridge” to expose the function from native SDK to JavaScript. In order to implement it into React Native.

Second, TypeScript – static typing programming language. You can opt-in for using TypeScript to create React Native mobile application.


  • Flow

Optional library for JavaScript to static typing. Which help developer reduce run-time and compiled error. Generally help in produce a more stable and debuggable JavaScript application.


  • Continuous Integration and Continuous Delivery (CI/CD)

Get to know which CI/CD would help you or your project. Automated your deployment to Play Store and App Store can save tons of time from work flow. Nevercode, CircleCI or Bitrise is CI/CD platform that you can try to automated your React Native deployment. Sign up an account from Bitrise or CircleCI as both of them provided free plan with limitation for developer.


Provide update over the air without go through App Store and Play Store as long as the update don’t violate the App Store and Play Store T&C and modify the purpose of your app.

#Library

  • ReactJS

It’s good to know the ReactJS before start React Native. Why ? To avoid writing crappy code that will bring tech debt to the code base. At least, developers should know the lifecycle, state, props and rendering in ReactJS. Get to know how it work, definitely make your life better.


  • UI Library

UI library is a collection of components that is styled with pre-define themes. More or less similar to Bootstrap or Foundation for web. Some of the famous UI library is Native Base, React Native Paper (Material UI theme) , React Native Elements and etc. (Personally, I would recommend Native Base as in the library quite mature. But, be aware of some pitfall from Native Base)


#Idea

  • Move fast and break things.

A famous quote from Facebook. ReactJS and React Native are inherit the spirit from the quote. Both library is moving fast, break some stuffs (Early version of React Native) and introduce new tools or API. So, if you are looking for tutorial. Please look for those new tutorial instead of month(s) ago or year(s) ago tutorial. Keep yourself updated from ReactJS blog or React Native blog. (Stop using componentWillMount for newbie, it’s deprecated)


  • File Structure

Learn to know the size of your app or discuss with your team first. It help you have a better overview to structure your folders and files.


Get to know the idea of Closure. It’s help you understand the functional programming pattern. It do help you understand how to create a Higher order Component using Closures but for component.


A good technique for optimizing React Native application. Only load specific resource when needed.


  • Composition

Get yourself familiar with the idea of composition instead of inheritance. For example, it’s sort of like playing lego. Joining small pieces of lego to build something.


Conclusion

Basically, this is a list of my personal experience regarding React Native stack. It’s not limited to this list. The rest, such as how to manage environment for dev, staging or production ? How to manage configuration in build.gradle or plist.info file ? Unit testing, end to end testing.

Out of topic, Why choose React Native ? My answer is because of declarative UI.

Thanks for reading. Have a nice day.

Custom React Hooks

Reusable Custom React Hooks

tl;dr

Create a custom React Hooks that can be share among ReactJS and React Native codebase.

A custom React Hooks for functional component. The purpose is more or less similar to my previous post Reusable HoC – Part 1 and Resuable HoC – Part 2.

This post is assuming you have a basic understanding about React Hooks. As this post will not explain what is React Hooks.

Outline

Let’s start

Step 1 – Use React Hooks in functional component

Let’s start, create a file and give it a name called demo.js or whatever you like.

import React, { useState, useEffect } from "react";
import { FlatList } from "react-native";
import { Container, Content } from "native-base";
import NewsItemWithAPIUsingHooks from "../components/NewsItemWithAPIUsingHooks";

export default function DemoReactHooks() {
  const [topStory, setTopStory] = useState([]);

  useEffect(() => {
    async function fetchTopStories() {
      try {
        const res = await fetch(`https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty`, {
          method: "GET",
        }).then(res => res.json());

        if (res) {
          setTopStory([...res]);
        }
      } catch (error) {
        // handling error
      }
    }

    fetchTopStories();
  }, []);

  function renderStory({ item, }: { item: Object }) {
    return (
      <NewsItemWithAPIUsingHooks storyID={item} />
    );
  }

  return (
    <Container>
      <Content>
        <FlatList data={topStory} keyExtractor={(item, index) => `${item}-${index}`} renderItem={renderStory} />
      </Content>
    </Container>
  );
}

As the code show above, we fetch the topstory news at useEffect which is when the component mounted. Then, display the result – a list of story Id in the <FlatList />.

What if we want to reuse the same stateful logic somewhere else or maybe sharing the same codebase with React Native ?

  • Copy paste
  • Custom React Hooks

Step 2 – Extracts React Hooks into custom React Hooks

Well, let’s extract current structure into a custom React Hooks for reuse purpose.

Now, create a new file called useStory.js and copy paste below code into the file.

import { useState, useEffect } from "react";

export default function useStory(storyType: string) {
  const [response, setResponse] = useState({});
  const [isLoading, setIsLoading] = useState(false);
  const [hasError, setHasError] = useState(false);

  useEffect(() => {
    async function fetchStory(storyType: string) {
      try {
        setIsLoading(true);
        const res = await fetch(`https://hacker-news.firebaseio.com/v0/${storyType}.json?print=pretty`, { method: "GET" }).then(res => res.json());

        if (res) {
          setResponse(res);
          setIsLoading(false);
        }
      } catch (error) {
        // handling error
        setHasError(true);
      }
    }

    fetchStory(storyType);

    return () => {
      // componentWillUnmount, cancel API request or event listener
      // To prevent memory leaked
    };
  }, [storyType]);

  return {
    response,
    isLoading,
    hasError
  };
}

Now, we extract the stateful logic into a custom Hooks. The naming convention of custom React Hooks should prefix with use. There is a eslint plugins to help developer linting the naming of custom React Hooks.

useEffect – fetching API request and return a function that will be execute when the component unmount. Cancel an API request or remove event listener will be done inside return function.

Access to the state ? Return the state at the end of the custom React Hooks.

Step 3 – Refactor component to use custom React Hooks

import React from "react";
import { FlatList } from "react-native";
import { Container, Content } from "native-base";
import NewsItemWithAPIUsingHooks from "../components/NewsItemWithAPIUsingHooks";
import useStory from "../hooks/useStory";

export default function DemoReactHooks() {
  const topstories = useStory("topstories");

  function renderStory({ item, }: { item: Object }) {
    return (
      <NewsItemWithAPIUsingHooks storyID={item} />
    );
  }

  return (
    <Container>
      <Content>
        <FlatList data={topstories.response} keyExtractor={(item, index) => `${item}-${index}`} renderItem={renderStory} />
      </Content>
    </Container>
  );
}

Now, it’s much more cleaner than before. useStory React Hooks is able to reuse in other component even in React Native without copy paste.

Conclusion

Finally, come to an end of the series of creating reusable stateful business logic, to share codebase among web and mobile app (ReactJS and React Native).

To maximize the reusability of HoC or custom React Hooks, it’s better to keep it as simple as possible and not coupling too much third party JavaScript library.

A reusable HoC or custom React Hooks is good for Junior developer or someone who is new to a project’s codebase or team. This way can reduce the time to study and get familiar with the codebase and make them onboard as soon as possible. They can be more focus on the input and output.

Maintenance issue. Now, fixed a bug in business logic only done once instead of twice or more.

Separation of concern. Separate business logic out of the presentation view. Easier for unit testing and update UI especially when the component grow larger

Thanks for reading. Full source code can be obtain from my github repository

Higher Order Component - Part 2

Higher order Component in React Native – Part 2

tl;dr

In my previous post, I explain about write a HoC, encourage more reuse of codebase. This post I will try to explain more example, to make a HoC more flexible (certain use case).

Dynamic Props

import React, { Component } from "react";

export default function withTopStories(WrappedComponent) {
  return class extends Component {
    constructor(props) {
      super();

      this.state = {
        stories: []
      }
    }
    
    componentDidCatch() {
      // error handling
    }

    async componentDidMount() {
      try {
        const res = await fetch('https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty', { method: "GET" }).then(res => res.json());

        if (res) {
          this.setState({ stories: res })
        }
      } catch (error) {
        // error handling
      }
    }

    render() {
      const { stories } = this.state;
      return <WrappedComponent {...this.props} stories={stories} />
    }
  }
}

Given example above from my previous post. We facing an issue as following:

  • It’s only allow developer to fetch topstories resource from HackerNews API. (may not be a bad case, sometimes)
// latest item from HackerNews API
https://hacker-news.firebaseio.com/v0/maxitem.json?print=pretty

// Up to 500 top stories / new stories / best stories
https://hacker-news.firebaseio.com/v0/[topstories|newstories|beststories].json?print=pretty

// Up to 200 Ask / Job / Show stories
https://hacker-news.firebaseio.com/v0/[askstories|showstories|jobstories].json?print=pretty

It’s okay that if you build each API endpoint a standalone HoC (For single responsibility pattern or maybe due to business logic is vary). What if you can reuse the same HoC even for some combination too.

Below is an example, make it as dynamic as possible (Please prioritize maintaining rather than dynamic use case, as in most of the time the most dynamic function is the one hardest to maintain and debug, trade off)

import React, { Component } from "react";

export default function withStories(endpoint, dynamicProps) {
  return (WrappedComponent) => {
    return class extends Component {
      constructor(props) {
        super();

        this.state = {
          stories: []
        }
      }
    
      componentDidCatch() {
        // error handling
      }

      async componentDidMount() {
        try {
          const res = await fetch(endpoint, { method: "GET" }).then(res => res.json());

          if (res) {
            this.setState({ stories: res })
          }
        } catch (error) {
          // error handling
        }
      }

      render() {
        const { stories } = this.state;
        return <WrappedComponent 
                {...this.props} 
                {...{ [dynamicProps]: stories }} />
      }
    }
  }
}

Since, HoC is a JavaScript closures. We can modify it to take in and holding additional parameter – endpoint and dynamicProps.

This way a developer can passing any endpoint (Let’s limited to certain purpose or use case, otherwise it will caused the HoC hard to maintain at the end).

Then, the dynamicProps define how a developer accessing the the props. dynamicProps is defined by developer, for two purpose.

  • The business logic / HoC is encapsulated. Developer more focus on input and output. Less worry for handling business logic.
  • To prevent props collision, when it’s come to combine two or more HoC
export default function withStories(endpoint, dynamicProps) {
  return (WrappedComponent) => {
    return class extends React.Component {
      ...
      render() {
        return <WrappedComponent {...{ [dynamicProps]: value }} />
      }
    }
  }
}

Combines two or more HoC

One of the way, to combine multiple HoC as below:

withHoCOne(withHoCTwo(withHoCThree(App)));

Well, it solve the combine multiple HoC issue. At the same time, it create another issue – readability. Yes, the code is hard to read.

Here, recommend some utility to help solving the issue and maintain the code readability.

compose – from several package

This way we can combine multiple HoC without sacrifice the code readability.

compose(
  withHoCOne,
  withHoCTwo,
  withHoCThree,
)(App)

Beware of

Redundant props or some unused props. In ReactJS, a component is re-render when the props changed. So, if a HoC passing some redundant props and the value had changed it will caused the component to re-render. A bug that is hard to trace.

Conclusion

Wisely use of HoC is promoting the reuse of codebase. Improve maintainability and component unit testability.

Thanks for reading.  😁

Higher Order Component

Higher order Component in React Native – Part 1

tl;dr

Wrapped API in Higher order Component to promote reusable component and static component unit testing

Scenario

Create a component, fetching API/resources in componentDidMount. Most of the tutorial, start this way. This is the easier way for anyone to learn fetching API/resources in ReactJS/React Native. I don’t against this pattern. But, things go complicated in real world business. Fetching multiple API or reuse the same business logic (stateful business logic).

Copy paste might solve your problem and create duplicated code or even worse the codebase will grow very fast. What if you can done the job with a function instead of copy paste ? This way you can separate the business logic and presentation component, making component smaller and modular. It will be a good investment in long terms (reduce tech debt).

Wrapped API/resources fetching or business logic in Higher order Component promote the reusable of presentation component and business logic.

** A plain Higher order Component without too much dependencies allow you to share the same code with ReactJS for web or vice versa.

// Example

export default class App extends React.Components {
  componentDidMount() {
    fetch();
    fetch();
  }

  render() {
    return <View>
      {resources}
      {resources}
    </View>
  }
}

How to utilize Higher order Component (HoC) ?

*From here onward, will calling HackerNews API for demo purpose*

Let’s start with fetching topstories from HackerNews API – return a list of story ID. Then, fetching another API for particular story details.

// list of story ids
https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty

// details of story
https://hacker-news.firebaseio.com/v0/item/121003.json?print=pretty

First, let create the first Higher order Component (HoC), for topstories from HackerNews API.

import React, { Component } from "react";

export default function withTopStories(WrappedComponent) {
  return class extends Component {
    constructor(props) {
      super();

      this.state = {
        stories: []
      }
    }
    
    componentDidCatch() {
      // error handling
    }

    async componentDidMount() {
      try {
        const res = await fetch('https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty', { method: "GET" }).then(res => res.json());

        if (res) {
          this.setState({ stories: res })
        }
      } catch (error) {
        // error handling
      }
    }

    render() {
      const { stories } = this.state;
      return <WrappedComponent {...this.props} stories={stories} />
    }
  }
}

In Higher order Component, component was parameterizedWrappedComponent. In above example, the HoC can now be reuse to any component that need to access the topstories resources.

The withTopStories will fetching the topstories resources from HackerNews API and set to the state – stories. Later on, passing the resources as a props to the parameterized component – WrappedComponent.

Accessing the injected props

Now, we want to access the injected props. Maybe a <FlatList />

import React, { Component } from "react";
import { FlatList, Text } from "react-native";
import withTopStories from "path/to/withTopStories";

class App extends Component {
  renderStories({ item, index }) {
    return <Text>{item}</Text>
  }

  render() {
    const { stories } = this.props;
    return <FlatList 
            data={stories} 
            renderItem={this.renderStories} 
            keyExtractor={(item, index) => `${item}-${index}`} 
           />
  }
}

export default withTopStories(App);

Then, we will render a list of number which is the story’s ID from HackerNews API. In order to get the details, we need the ID to fetch another API.

Yet another HoC for another endpoint

Then, we create another HoC for fetching the details of story by the ID.

import React, { Component } from "react";

export default function withStoryDetails(WrappedComponent) {
  return class extends Component {
    constructor(props) {
      super();

      this.state = {
        details: {}
      }
    }

    async componentDidMount() {
      try {
        const { storyID } = this.props;
        const details = await fetch(`https://hacker-news.firebaseio.com/v0/item/${storyID}.json?print=pretty`, { method: "GET" }).then(res => res.json());

        if (details) {
          this.setState({ details })
        }
      } catch (error) {
        // error handling
      }
    }

    render() {
      const { storyID, ...rest } = this.props;
      const { details } = this.state;
      return <WrappedComponent {...rest} details={details} />
    }
  }
}

Now, we need a component to display the details. Let’s create one. This time we won’t wrapped it directly, instead we will create a static version. This allow us easy to mock the data and unit testing the component.

import React from "react";
import { Text } from "react-native"

export default function Story(props) {
  const { details, ...rest } = props;
  return <Text {...rest}>{details.title}</Text>
}
/**
 * Version 2.0
 */
import React, { Component } from "react";
import { FlatList } from "react-native";
import withTopStories from "path/to/withTopStories";
import withStoryDetails from "path/to/withStoryDetails";
import Story from "path/to/Story"; // the component we create above

const WrappedStory = withStoryDetails(props => {
  const { storyID, ...rest } = props;
  return <Story storyID={storyID} {...rest} />
});

class App extends Component {
  renderStories({ item, index }) {
    // replace the below component 
    return <WrappedStory storyID={item} />
  }

  render() {
    const { stories } = this.props;
    return <FlatList 
             data={stories} 
             renderItem={this.renderStories} 
             keyExtractor={(item, index) => `${item}-${index}`} 
           />
  }
}

export default withTopStories(App);

Closing

Now, both React Native and ReactJS can share the HoC. Use it on mobile or web. Wrapped it with any presentation component without duplicate code. Easier to unit testing static component.

React Native 0.60

What’s new in React Native 0.60 ?

tl;dr


What’s new introduce in React Native 0.60 ?

  • Improvement on accessibility for both Android and iOS
  • Nested <View />within <Text /> components, but with limitation
  • touchSoundDisabled props introduce to <Button />, <Touchable />and <TouchableWithoutFeedback />for android only
  • Included a default eslintrc.js config file
  • Migration to AndroidX (Enabled by default)
  • Auto linking (Linking native modules)

  • New Introduction App Screen

Starting React Native 0.60 above modules will totally remove from React Native Core. In order to utilize specific module you need to install it as separate library from react-native-community.


Why you shouldn’t to React Native 0.60 immediately ?

Bug fixes and new features that introduce in React Native 0.60 is great. But, you may want to take consideration before upgrading.

The main issue is that React Native 0.60 migrate to AndroidX where most of the library has not ready yet for this changes. So, in order to upgrade to React Native 0.60. You may need to ensure all the third-party plugins/library in your application is compatible with AndroidX. Although, soon they will migrate to support AndroidX too. But, not at the moment.


Upgrade Helper

Upgrade helper – upgrade made easy. A new web tools that can help developer upgrading their react native easily. As it is comparing the different of two different react native version and show the difference side by side in web browser.

Google API for Android

tools:replace=”appComponentFactory”

tl;dr

17 June 2019 – Dear Google release new update for Google Play service and Firebase. Out of sudden, react native application failed at compile time and keep complaining the following message.

Manifest merger failed : Attribute [email protected] value=(android.support.v4.app.CoreComponentFactory) from [com.android.support:support-compat:28.0.0] AndroidManifest.xml:22:18-91
    is also present at [androidx.core:core:1.0.0] AndroidManifest.xml:22:18-86 value=(androidx.core.app.CoreComponentFactory).
    Suggestion: add 'tools:replace="android:appComponentFactory"' to <application> element at AndroidManifest.xml:5:5-19:19 to override.

But, why ?

Why is it related to Google releasing new update for Google Play service and Firebase ?

Google API for Android

Here is why. The update will required you to update your app to use Jetpack (AndroidX). Hence, you need to migrate your app to AndroidX, if you haven’t migrate yet. Android official website do provide the guide to do so – Migrating to AndroidX.

Adding the following to your gradle.properties file in the root project folder.
For react-native project – rootProject/android/gradle.properties

android.useAndroidX=true
android.enableJetifier=true

How about React Native ?

React Native plan to support AndroidX in the next release RN 0.60. React Native June Update. So, most likely that third-party React Native library will follow afterward.

If your react native app facing any issue as describe above, I suggest a few to solve your problem.

#1 If your app use any Google API service

In your build.gradle,

dependencies {
  // Please do this
  implementation "com.google.android.gms:play-services-gcm:16.1.0"

  // Not this, as this line will always fetching the latest version 
  // implementation "com.google.android.gms:play-services-gcm:+"
}

Locked the version of Google API service, instead to taking the latest version can save you from the error. As long as your version is not the one in latest version, then you are good to go.

#2 Updating third-party React Native library

So far, what I know is that react-native-device-info is releasing a patch to fixes this issue. Maybe there is other library affected as well, that you may need to find it out.

#3 Migrating to AndroidX

But, this is not a reliable way for React Native, especially if your app depend on a huge amount of third-party library.

For more details, please refer to this github issue.

Thanks for reading. Have a nice day.