Building React-Native Hackernews App — Part 1

Using Redux, Redux-Saga, Immer, and a custom Navigation Solution

Ilija
ITNEXT

--

Simulated GPRS network, on android emulator in dev mode (a lot of console.log)

Recently I have played with React for web & NodeJS and not so much with React Native. To refresh my memory I started a new project to create a performant HackerNews mobile client made with React Native. For this project I wanted to use some libraries which I haven’t used before such as Immer and Redux-Saga. One of my focuses was to create a very performant UI experience thus I choose to implement my own navigation solution which will be discussed in another part of the series.

In this part we are going to focus on the setup of redux, redux-saga, and the starting screen of the app, which shows the Top, Show, Ask, New stories from hackernews. This article is an overview on what choices I made and the best practices.

The work in progress and the source can be found here: https://github.com/Rusfighter/HackerNewsRN

The Stack

  • React Native (no Expo, read further for my choice)
  • Redux for state management & caching
  • Redux-Saga for side-effects
  • Immer for immutability
  • Render Props for Navigation
  • Fractal project structure

React-native-cli vs Expo-cli

I choose not to use the expo starter kit for one simple reason: the dependencies you get which I will never use and thus making the binaries for the deployements very big (20mb+). Another reason is to have more control on which dependencies I can use in the future (react-native-firebase?).

Hackernews

The reason I choose to create a hackernews clone app is really simple. I didn’t want to code any backend and wanted to just focus on the client part, hence I looked on the available public API’s and what people have created so far with them. One of the projects: https://github.com/hidroh/materialistic which is a hackernews app made in Java for Android looked very well. I wanted to see how much I could push react-native to make it performant as a native app or even better. The public API of hackernews is available here: https://github.com/HackerNews/API

Lets start

First you need to setup your environment for react native, the docs can be found here: https://facebook.github.io/react-native/docs/getting-started.html on the tab: Building Projects with Native Code

After you can clone my repo on https://github.com/Rusfighter/HackerNewsRN

run: npm install & react-native link react-native-vector-icons

All the app related code can be found in /src directory. I choose to use a fractal structure pattern (read this article if you interested) for my files and directories which I find working well. The entry point of the app is /src/App.js

Redux + Redux Saga + Immer

For state management I choose the well-known redux in combination with redux-saga. It is known that side-effects (API requests, etc) are a pain in the ass and thats why people made things such as Redux Saga. One of the benefits of redux saga is that I can keep my reducers really dumb, simple and put the hard logic in the middleware (redux saga). To keep my reducers immutable I used a package called immer. This package worked really well with my helper function which let you avoid having switch cases. With this combination you can very easy create pure reducers!

The end result of the helper function with which you can create reducers:

The reducers will now look something like:

As you can see we can directly push to the array (line 19) and assign to an object (line 21), all thanks to immer. Also we don’t need to use any switch cases anymore.

Keep the redux state normalized

It is always best practice to keep the state normalized, even if you think you don’t need it now, you will. Seriouysly, just do it!!!
Read more here how to create a normalized state and why you should: https://redux.js.org/recipes/structuringreducers/normalizingstateshape

Redux-Saga

Since this is a hard topic you should dive into it (or just copy), I will tell the things & tricks I have used so far to make this a short section:

At the moment I use redux-saga only for API requests (src/utils/api.js). One pattern I have used is to make sure that a unique request is never twice in the air and thus reducing the network traffic. To show you what I mean look at this code (sorry for bad name convention, fetchItem = watchGetItem):

The fetchItem functions is waiting for a event of type GET_ITEM. When this event happens, it caches the execution of the getItem function by id (line 43) and executes the getItem (line 8) function in the background and continuos waiting for a next GET_ITEM event. However, if a GET_ITEM event is dispatched with the same id and the getItem function is still busy for that id, the getItem function will not be executed. The result is that we do not need to manually cancel http requests or wasting requests which gives a far better network control. This is one of the powers of using redux-saga! This functionality is familiar to takeLatest effect which is implemented in redux-saga. To get familiar with redux-saga I recommend to read their docs and watch some youtube videos.

See the configuration of redux and its middlewares in /src/store.js. The files are ducks which means that reducers, constants and actions creators are declared in one file per type (for example src/redux/comments.js contains reducer + action creators and constants).

The Home Screen

One of my choices was to keep the navigation simple and quick. Therefore I choose a bottom tabbar layout for the homepage where the user can navigate quickly between the story lists (top, new, etc). For this I used a packaged called react-native-tab-view which in my practice has the best performance if you use it correctly.

The trick is to lazy load the tabs (which is not an option as far as I know in react-native-tab-view). See the _renderScene function and ask in comments if something is not clear! The full source of this part is in /src/screens/Home.js

Top, New, Ask, Show

These tabs are very familiar in looks and functionality, the only difference is the data selection from the store and the dispatched events for getting data.

Source of Ask Tab (src/screens/home/Ask.js):

Two take aways:

  • If the render method is not dependent on props except if it is a function and has no state, implement a shouldComponentUpdate method which returns false. It is always good to think about this method and know when you really should re-render the component (which is expensive most of the time)
  • If you using action creators, you don’t need to wrap those in a dispatch function, react-redux does it automatically for you (line 26)

Story List

One take away:

  • Since there is only an API request to fetch the whole list of Asks stories, we manually implemented a pagination (line 11), when the user scrolls to bottom of the list, more elements from the store are added to the FlatList (line 22)

StoryListItem.js

Three take aways:

  • We only fetch story if the item is not defined in the redux store, thus already applying application runtime caching for a story (line 6)
  • We only re-render item if the item has been changed (performance)
  • We have 2 representations, one dummy item which is just some fancy loading effect like medium has and one real representation

See the full source of this file in src/screens/home/StoryListItem.js

The result:

Simulated GPRS network, on android emulator in dev mode (a lot of console.log)

I hope I gave some insights of what I think are the best choices for several scenarios. If you think you have better solutions or want to contribute to the project feel free to PM me or create a PR!

This was the end of part one. I hope you enjoyed this article and learned something. In the next part(s) we will talk about navigation, rendering comments, handling network errors, caching, rendering html and much more…

The current state of project has custom navigation, html rendering and comments page. So go ahead and play with it and tell me what I should explain in the next part about those components.

One more thing, I don’t plan to publish this app on any of the stores so you are free to do that, just put some reference to github repo :)

Till the next time!

--

--