[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.

Dialog and Custom React Hooks

tl;dr

We know that custom React Hooks allow us to reuse stateful logic. How can we utilize it with the Dialog component in Material UI ? Below, I will demonstrate the benefit of breaking component into individual pieces and utilize custom React Hooks to write cleaner React component.

Let’s Start

Install Material-UI before continue. As we will use the Dialog component from this library.

yarn add @material-ui/core

#1

Now, assume that we have one Dialog composed component base on Material-UI.

import React, { useState } from "react";

import { Container, Button, Typography, Dialog, DialogContent } from "@material-ui/core";

export default function App() {
  const [open, setOpen] = useState(false);

  function handleOnClose() {
    setOpen(false)
  }

  function handleOnOpen() {
    setOpen(true)
  }

  return <Container>
    <Typography component="div" align="center">
      <Button 
        variant="contained" 
        color="primary" 
        onClick={handleOnOpen}
      >
        Add
      </Button>
    </Typography>

    <Dialog open={open} onClose={handleOnClose}>
     {/* Please mock your content here */}
     <DialogContent />
    </Dialog>
  </Container>
}

#2

Now, let’s say, we are adding another Dialog. We humans are easier to get distracted. This apply to software development as well, as the codebase growth, it will add difficulty to debugging process. Because we are distracted by the others part of the code. So, how we going to keep our UI code as clean as possible ?

By separate the child component out of the parent. Let’s take a look, below code.

A Dialog component for adding new item.

import React from "react";
import { Dialog, DialogContent } from "@material-ui/core";

export default function DialogAddNewItem(props) {
  return <Dialog {...props}>
    { /* Please mock your content */ }
    <DialogContent />
  </Dialog>
}

Yet, we have another Dialog for editing existing item. We called it DialogEditItem.

import React from "react";
import { Dialog, DialogContent } from "@material-ui/core";

export default function DialogEditItem(props) {
  return <Dialog {...props}>
    { /* Please mock your content */ }
    <DialogContent />
  </Dialog>
}

This way we can add functionality to individual Dialog component without worrying that it would break each others. Even changing the UI.

So, we will re-write our App component as below.

import React from "react";

import DialogAddNewItem from "./DialogAddNewItem";
import DialogEditItem from "./DialogEditItem";

export default function App() {
  const [open, setOpen] = useState(false);
  const [openEditDialog, setOpenEditDialog] = useState(false);

  function handleOnClose() {
    setOpen(false)
  }

  function handleOnOpen() {
    setOpen(true)
  }

  function handleOnOpenEditDialog() {
    setOpenEditDialog(true)
  }

  function handleOnCloseEditDialog() {
    setOpenEditDialog(false)
  }

  return <Container>
    <Typography component="div" align="center">
      <Button 
        variant="outlined" 
        color="primary" 
        onClick={handleOnOpenEditDialog}
      >
        Edit
      </Button>
      <Button 
        variant="contained" 
        color="primary" 
        onClick={handleOnOpen}
      >
        Add
      </Button>
    </Typography>

    <DialogAddNewItem open={open} onClose={handleOnClose} />
    <DialogEditItem 
      open={openEditDialog} 
      onClose={handleOnCloseEditDialog} 
    />
  </Container>
}

It’s better, isn’t it ? Whenever we expand the functionality in DialogAddNewItem and DialogEditItem. It will not affect the codebase of App.

#3

Now, we having another issue. The state to control the Dialog component. Let’s say, we wanna add another Dialog. Then, we have to come out another new naming for the state. (I’m always lack of idea to think of new name)

Can we reuse the stateful logic ? Yes, we can. (Thanks React Hooks. You save me from thinking a new variable name)

import { useState } from "react";

export default function useDialog() {
  const [open, setOpen] = useState(false);

  function onOpen() {
    setOpen(true);
  }

  function onClose() {
    setOpen(false)
  }

  return { open, onOpen, onClose }
}

#4

Next, re-write our App component yet again.

import React from "react";

import DialogAddNewItem from "./DialogAddNewItem";
import DialogEditItem from "./DialogEditItem";
import useDialog from "./useDialog";

export default function App() {
  const dialogAddNewItem = useDialog();
  const dialogEditItem = useDialog();

  return <Container>
    <Typography component="div" align="center">
      <Button 
        variant="outlined" 
        color="primary" 
        onClick={dialogEditItem.onOpen}
      >
        Edit
      </Button>
      <Button 
        variant="contained" 
        color="primary" 
        onClick={dialogAddNewItem.onOpen}
      >
        Add
      </Button>
    </Typography>

    <DialogAddNewItem 
      open={dialogAddNewItem.open} 
      onClose={dialogAddNewItem.onClose} 
    />
    <DialogEditItem 
      open={dialogEditItem.open} 
      onClose={dialogEditIte.onClose} 
    />
  </Container>
}

Conclusion

This is one of the example to utilize custom React Hooks and write cleaner React component and not only limited to. The same concept can be apply to React Native as well.

Let’s write a better and cleaner React component with me. Thanks for reading. Have a nice day.  😁

Remove duplicated element

How to remove duplicate elements in JavaScript ?

tl;dr

[Update on 2 Dec 2019]

Ways to remove duplicated elements in JavaScript

  • Array.prototype.filter()
  • JSON
  • Set

#1 Using Array.prototype.filter()

const duplicated = [
  "John",
  "Johnny",
  "Jonathan",
  "Christine",
  "Conan",
  "Christine",
  "Johnny"
];

duplicated.filter(
  (element, index) => duplicated.indexOf(element) === index
)

// Expected result
// [ 'John', 'Johnny', 'Jonathan', 'Christine', 'Conan' ]

#2 JSON

const duplicated = [
  "John",
  "Johnny",
  "Jonathan",
  "Christine",
  "Conan",
  "Christine",
  "Johnny"
];

const unique = {};

duplicated.forEach(val => {
  unique[val] = val;
});

console.log(Object.values(unique));
// or console.log(Object.keys(unique))
// Expected result
// [ 'John', 'Johnny', 'Jonathan', 'Christine', 'Conan' ]

#3 Set

const duplicated = [
  "John",
  "Johnny",
  "Jonathan",
  "Christine",
  "Conan",
  "Christine",
  "Johnny"
];

// first, how to convert Array to Set
const setFromArray = new Set([]);

// second, how to convert Set back to Array
const arrayFromSet = [...new Set([])];

// So, in order to remove duplicate elements using Set
// first, convert the Array to Set
// Then, convert back to Array
console.log([...new Set(duplicated)]

// or console.log(Object.keys(unique))
// Expected result
// [ 'John', 'Johnny', 'Jonathan', 'Christine', 'Conan' ]
React Hooks - useState and useEffect

React Hooks – useState and useEffect

tl;dr

Demo for react hooks usage – useState and useEffect. As I found out I haven’t write any post about the React Hooks yet.  😅

The first thing I look into React Hooks is the ability to reuse stateful logic (At that time, I haven’t familiar with the concept of Higher order Component yet.) Thus, I’m excited for the React Hooks where I found it can reduce a lot of repeated stateful logic.

Introduction

React Hooks was introduced in version 16.8. It allow you to interact with state in functional component.

#1 useState

@ Example 1

import React, { useState } from 'react';

function Example() {
  const [value, setValue] = useState(0);
}

@ Example 2

import React from 'react';

function Example() {
  const [value, setValue] = React.useState(0);
}

[value, setValue] – array destructuring in ES 6.

useState is return two element.

  • First element is the value of a state
  • Second element is the function to update the state
  • Naming convention doesn’t matter, but the order does

** How to swapping element using array destructuring ?

#2 useEffect

useEffect – a combination of componentDidMount, componentDidUpdate and componentWillUnmount.

@ Example

import React, { useEffect } from 'react';

function Example() {
  useEffect(() => {
    // synchronous callback
    windows.addEventListener();

    // cleanup, equivalent to componentWillUnmount
    return () => {
      windows.removeEventListener();
    }
  }, [dependencies])
  // dependencies will handle whether to re-run the useEffect or not
  // if dependencies value remain the same. it will skip useEffect
  // otherwise, execute the useEffect
}

@ Example – Execute once

import React, { useEffect } from 'react';

function Example() {
  useEffect(() => {
    // Equivalent to componentDidMount
    // Code, execute once on when the component mounted
  }, [])
}

@ Example – Execute every re-render

import React, { useEffect } from 'react';

function Example() {
  useEffect(() => {
    // Code, execute on every re-render
  })
}

@ Example – Execute only when dependencies updated

import React, { useEffect } from 'react';

function Example() {
  useEffect(() => {
    // Equivalent to componentDidUpdate
    // Execute if dependencies updated
    // Otherwise, skip useEffect
  }, [dependencies])
}

@ Example – Asynchronous in useEffect

import React, { useEffect } from 'react';

function Example() {
  // You are not allowed to do this, the callback is always synchronous
  useEffect(async () => {
    // Code
  }, [dependencies])
}

// you can try below
function Example() {
  useEffect(() => {
    async fetchSomething() {
      // Code
    }

    fetchSomething();
  })
}

#3 Yet another example

@ Example – Fetching blog posts

import React, { useState, useEffect } from 'react';

function MyBlog() {
  const [posts, setPosts] = useState([]);
  const [selectedCategories, setSelectedCategories] = useState(new Set([])); // Selected categories for filter
  const [page, setPage] = useState(0); // blogposts pagination
  const [loading, setLoading] = false;

  useEffect(() => {
    // Declare a function to fetch blogpost from WordPress API
    async fetchPosts(blogPage, blogCategory) {
      try {
        setLoading(true);
        const res = await fetch(
          `https://blog.yikkok.com/wp-json/wp/v2/posts?_embed&page=${blogPage}&categories=${blogCategory}&per_page=5`,
          {
            method: "GET"
          }
        ).then(response => response.json());

        if (res) {
          setPosts([...res]);
          setIsLoading(false);
        }
      } catch (error) {
        // handling error
        setIsLoading(false);
      }
    }
   
   const selectedCategoriesStr = Array.from(selectedCategories).join(",");
   fetchPosts(page, selectedCategoriesStr)
  }, [page, selectedCategories])
  // Because I'm using page and selectedCategories inside useEffect,
  // Thus, I pass the page and selectedCategories as the dependencies,
  // As I want the useEffect to re-run whenever either one updated

  function handleUpdatePage(pageNumber) {
    setPage(pageNumber);
  }

  function handleUpdateSelectedCategories(selected) {
    // A closure
    return () => {
      // Just for demo purpose, it may contain some bugs...
      const cloned = new Set(selectedCategories);

      if (cloned.has(selected)) {
        cloned.delete(selected)
      } else {
        cloned.add(selected)
      }

      setSelectedCategories(cloned);
    }
  }

  return (render UI)
}

#4 Custom React Hooks

Let’s write a custom React Hooks, Using useState and useEffect.

import React, { useState, useEffect } from 'react'

// React Hooks naming convention, always prefix with 'use'
export default function useBlogPosts() {
  const [posts, setPosts] = useState([]);
  const [selectedCategories, setSelectedCategories] = useState([]);
  const [page, setPage] = useState(0);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");

  useEffect(() => {
    async fetchPosts(blogPage, blogCategory) {
      try {
        setLoading(true);
        const res = await fetch(
          `https://blog.yikkok.com/wp-json/wp/v2/posts?_embed&page=${blogPage}&categories=${blogCategory}&per_page=5`,
          {
            method: "GET"
          }
        ).then(response => response.json());

        if (res) {
          setPosts([...res]);
          setLoading(false);
        }
      } catch (error) {
        // handling error
        setLoading(false);
        setError(error.message);
      }
    }
   
   const selectedCategoriesStr = Array.from(selectedCategories).join(",");
   fetchPosts(page, selectedCategoriesStr)
  }, [page, selectedCategories]);

  function handleUpdateSelectedCategories(selected) {
    const cloned = new Set(selectedCategories);

    if (cloned.has(selected)) {
      cloned.delete(selected)
    } else {
      cloned.add(selected)
    }

    setSelectedCategories(cloned);
  }

  function handleUpdatePage(pageNumber) {
    setPage(pageNumber);
  }

  return { 
    posts,
    loading,
    error,
    page,
    selectedCategories,
    handleUpdateSelectedCategories,
    handleUpdatePage
  }
}

#5 Use the custom React Hooks

// Refactor MyBlog Component
import React from 'react';
import useBlogPosts from 'src/hooks/useBlogPosts';

function MyBlog() {
  const blog = useBlogPosts();

  // blog.selectedCategories
  // blog.handleUpdatePage
  // blog.handleUpdateSelectedCategories
  // blog.error
  // blog.loading
  // blog.posts
  // blog.page
}

With custom React Hooks, we can separate the stateful logic out of UI component, which make us easier to debug the code without being distracted by the UI code.

Please visit my github commit, how I refactor the source code into custom React Hooks.

Takeaway

useState

  • Access and interact state in functional component

useEffect

  • Synchronous
  • Work similar to componentDidMount, componentDidUpdate and componentWillUnmount

Custom React Hooks – Reuse stateful logic.

Thanks for reading.  😁

Callback JavaScript

Callback JavaScript

tl;dr

What’s callback function in JavaScript ? Callback function in plain JavaScript, Asynchronous and React

So, what is callback function ?

A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.

MDN Web Docs

Above is the definition of callback function from MDN Web Docs.

# Real life example ?

Scenario:

  1. Bob give a call to Alice.
  2. Alice isn’t pick up the call.
  3. Thus, Bob leave a message to Alice, to get back to Bob as soon as possible.
  4. Alice receive the message and give a call/message to Bob.

When Bob leave a message to Alice, we can imagine that Bob is passing a callback function and awaiting Alice responded to the callback function.

Callback function in plain JavaScript

/**
 * A function taking two params
 * @param {string} message - A string output to console
 * @param {function} reply - A function (callback function)
 */
function calls(message, reply) {
  console.log(message)
  reply()
}

/**
 * Output string to console
 */
function replyFromAlice() {
  console.log("I'm Alice, speaking please.");
}

function replyFromRobot() {
  console.log("The number you have dials is unreachable. Please leaves a message")
}

calls("May I speak to Alice ?", replyFromAlice);
// Expected from console.log
// May I speak to Alice ?
// I'm Alice, speaking please.

calls("Baby Shark Du...Du...Du...", replyFromRobot);
// Expected from console.log
// Baby Shark Du...Du...Du...
// The number you have dial is unreachable. Please leave a message

Asynchronous callback function

/**
 * Asynchronous callback function
 * Return "...\newline Hello ?" after awaiting 3seconds
 */
function asyncCalls(message) {
  return new Promise(resolve => { 
    console.log(message); 
    setTimeout(() => { 
      resolve("...\n Hello ?");
    }, 3000);
  });
}

/**
 * Asynchronous function, `then` and `catch` accessible from promise
 * resolve - will fall in `then` clause
 * reject - will fall in `catch` clause (I don't include `reject` in this post)
 */
asyncCalls("Du...Du...Du...").then(function(response) {
  // now, this is a callback function for `resolve()`
  console.log(response)
  // Expected
  // ...
  // Hello ?
}).catch(() => {})

// ES 6 - arrow function pattern(Don't be confused, both is doing the same thing)
// asyncCalls("Du...Du...Du...").then(response => {
//   console.log(response)
// }).catch(() => {})

then() clause is accepting a function as parameter. We can access the value return from resolve(value). For the case above, we are return the ...\n Hello ? in the resolve(). Hence, we can access the value in then().

Using callback in React

Pass the callback function as a props and get it fired whenever you need it.

  • Passing data from child component(s) to parent component(s)

@ Passing data from child component(s) to parent component(s)

import React from "react";

function Child(props) {
  function handleOnClick() {
    // Business logic

    const { onClickChild } = props;

    if (onClickChild) {
      // Fire the callback function to return value if needed.
      onClickChild("Demo for passing data to parent component");
    }
  }

  return <button onClick={handleOnClick}>Child</button>
}

export default function Parent() {
  function handleOnClickChild(valueFromChild) {
    console.log(valueFromChild);
    // Expected result
    // "Demo for passing data to parent" in console
  }

  return <Child onClickChild={handleOnClickChild} />;
}

On Child component, the onClick event on <button /> is accepting a callback function. Thus, we pass in a callback function handleOnClick().

Next, the onClickChild prop is another callback function for <Child /> component. It provide the functionality for parent component to retrieve the value from <Child /> or to continue action in parent component without intercept the default business logic in <Child />.

Conclusion

It’s good to know the fundamental of callback because it’s useful tools to handle asynchronous action or something you need to do-it-later.

** (Google about callback-hell. Async/await is come to rescue to prevent callback-hell (for promise) )

Thanks for reading.  😁

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.

AWS CodeCommit

Setup git repository using AWS CodeCommit service

tl;dr

Git – an important tool in software developer’s job scope/daily life. One of the skill that employers will look for in candidate profile (at least in my country – Malaysia). Most of the software developer will store the source code in cloud services, such as GitHub, BitBucket, GitLab, self-hosted git repository or etc. AWS CodeCommit is one of the service where we can store our source code in AWS.

Let’s start

#1 Introduction to AWS CodeCommit

  • Belongs to “Always free” category
    • First 5 active users
    • 50GB storage per month
    • 10, 000 git requests per month
  • AWS CodeCommit pricing

#2 Setup your first repository in AWS CodeCommit

AWS CodeCommit

Navigate to CodeCommit from the AWS Console.

AWS CodeCommit repository dashboard

Click “Create repository”

Create repository in AWS CodeCcommit

Take note that Repository names are restricted to alphanumeric characters, plus ‘.’, ‘_’, and ‘-‘. No space allowed in repository name.

#3 Setup SSH connection to AWS CodeCommit

Steps to setup SSH connection in AWS CodeCommit

Generate public/private key pair from terminal. (Please use GitBash or Windows Subsystem Linux(WSL) for Windows platform)

AWS provided clear instructions regarding how to generate ssh key for:

$ ssh-keygen
For illustration purpose

By default, ssh private and public key will store as /home/username/.ssh/id_rsa and /home/username/.ssh/id_rsa.pub respectively. You may want to name it something else to prevent override your default.

$ cat ~/.ssh/id_rsa.pub
For illustration purpose

Copy the output, where you will need to paste it in AWS CodeCommit.

Upload SSH public key to CodeCommit Credentials. First, click the account at the top navigation bar and click the “My Security Credentials”.

Click My Security Credential

Navigate to AWS CodeCommit credentials tab.

AWS CodeCommit credentials

Upload SSH public key.

Copy and paste the generated ssh public key into the field

After uploaded SSH public key, AWS will generated a SSH Key ID which acts as git user to access AWS CodeCommit service. Copy the SSH Key ID.

Uploaded SSH public key

Now, modify the ssh config file /home/username/.ssh/config. If you don’t have the config file, feel free generate one using your favourite terminal editor (vi, nano or etc).

Host git-codecommit.*.amazonaws.com
  User Your-SSH-Key-ID
  IdentityFile /path/to/your/private key
For illustration purpose

#4 Testing SSH connection

$ ssh git-codecommit.ap-southeast-1.amazonaws.com

If all the setup is correct, we will get the success message in terminal as below.

For illustration purpose

#5 Git and AWS CodeCommit

Clone from AWS CodeCommit

$ git clone ssh://git-codecommit.ap-southeast-1.amazonaws.com/v1/repos/personal-website

Add AWS CodeCommit as remote

$ git remote add aws ssh://git-codecommit.ap-southeast-1.amazonaws.com/v1/repos/personal-website

Push to AWS CodeCommit

$ git push aws

or

$ git push origin

#6 View Repository in AWS CodeCommit

Source code push from local machine

Thanks for reading.  😁

Online and Offline events in ReactJS

Online and offline events in ReactJS

A web apps that work both online and offline (or a slow network connection) has become relatively important in our life. Glad that browser provide related API for such event, online and offline events listener. How to do it in ReactJS using Hooks or HoC ?

#1 React Hooks

Below is a simple React Hooks for detect browser online and offline events.

import React, { useState, useEffect } from "react";

export default function useOnlineStatus() {
  const [online, setOnline] = useState(true);

  useEffect(() => {
    window.addEventListener("online", handler);
    window.addEventListener("offline", handler);

    // cleanup
    return () => {
      window.removeEventListener("online", handler);
      window.removeEventListener("offline", handler);
    }
  });

  function handler(event) {
    setOnline(navigator.OnLine);

    if (event.type === "online") {
      // handle stuffs when browser resume online
    } else {
      // handle stuffs when browser goes offline
    }
  }

  return { online }
}

@ Example – Use it in UI Component

Prerequisite

import useOnlineStatus from "useOnlineStatus";
import SnackBar from "@material-ui/core/SnackBar";

function Demo() {
  const status = useOnlineStatus();

  return (
    <>
      <SnackBar 
        open={!status.online} 
        anchorOrigin={{ vertical: 'top', horizontal: 'center' }} />
      <p>{status.online ? "Online" : "Offline" }</p>
    </>
  )
}

#2 Higher order Component

import React, { Component } from "react";

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

      this.state = {
        online: true
      }
    }

    componentDidMount() {
      window.addEventListener("online", this.handler)
      window.addEventListener("offline", this.handler)
    }

    componentWillUnmount() {
      window.removeEventListener("online", this.handler)
      window.removeEventListener("offline", this.handler)
    }

    handler = (event) => {
      this.setState({ online: Navigator.OnLine })

      if (event.type === "online") {
        // handle stuffs when browser resume online
      } else {
        // handle stuffs when browser goes offline
      }
    }

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

@ Example – Use it in UI Component

Prerequisite

  • Material-UI – SnackBar
import SnackBar from "@material-ui/core/SnackBar";
import withOnlineStatus from "withOnlineStatus";

function Demo(props) {
  const { online } = props;

  return (
    <>
      <SnackBar 
        open={!online} 
        anchorOrigin={{ vertical: 'top', horizontal: 'center' }} />
      <p>{online ? "Online" : "Offline" }</p>
    </>
  )
}

export default withOnlineStatus(Demo)

Thanks for reading.   😁

Migrate from a Share Hosting to DigitalOcean

tl;dr

Why migrate away from a share hosting ?
I’m looking for more control over my personal website and I can try experiment some production setup.

Current Setup

  1. Droplet (The cheapest option)  😂
  2. SSL by Let’s Encrypt
  3. Nginx with Brotli compression
  4. NextJS + MaterialUI

# Droplet

DigitalOcean Pricing table

I choose the cheapest option which is $5 per month. They are providing a lot of combination for users to pick a suitable droplet.

# Setup SSL

I’m setup my ssl certificate using Let’s Encrypt service.

# Nginx and Brotli compression

Brotli compression – a smaller size of files streams to end users. Thus, make your website feel performance.

I choose Ubuntu as my server’s operation system(OS). Then, configure the Nginx to serve my website. Recommended to install Nginx from source or Nginx official repository instead of from the package provided by the Ubuntu. This is because I have some problem when I want to add a new compiled module to existing Nginx. (tutorial)

// Further reading
SSL is required for setup brotli compression, however, you may skip Step 1 if you had setup SSL for your website previously.

# NextJS and Material-UI

Previous SPA website is written from scratch using ReactJS and Bootstrap. Then, I rewrite it using NextJS (Static rendering and Server Side Rendering) with Material-UI as the base design system. (Source code)

Bonus

NextJS deployment on Nginx’s reverse proxy.

Implementing git hooks automation process, whenever I push the new changes to my droplet, git hooks post-receive will checkout the new changes from master branch.

The process will be as below:

  1. Git push to specified droplet
  2. Checkout new changes
  3. Stop existing pm2 process (Depend on droplet resources, if your droplet have enough resources. Then, skip this step)
  4. Reinstall node_modules,
  5. Rebuild NextJS app
  6. Start/Restart pm2.
while read oldrev newrev ref
do
    if [[ $ref =~ .*/master$ ]];
    then
        echo "Master ref received.  Deploying master branch to production..."
        git --work-tree={path to webroot} --git-dir={source of directory} checkout -f
    else
        echo "Ref $ref successfully received.  Doing nothing: only the master branch may be deployed on this server."
    fi
done

stop_next_app &&
cd {/path/to/webroot} &&
yarn install && 
yarn build && 
start_next_app

Script is refer this tutorial

Thanks for reading. Have fun. 😀