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

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