Building Hacker News Desktop App with React and Electron

The story of Electron begins with Github’s open source code editor Atom. The goal was to build a desktop application with web technology. Github started looking for the right tool to build it. They tried Chrome Embedded Framework (CEF) and Node WebKit (nw.js) but soon realized they are gonna need something more specific. After drilling-down Github hired Cheng Zhao who was electron’s lead developer on Atom’s team. The project got open sourced in May 2014 and was renamed to Electron in April 2015.

What is it?

It’s a platform for building cross-platform desktop applications with web technologies. With electron, you can build your desktop application with HTML, CSS, and JavaScript. You can target all three platforms (Linux, windows, mac) with one code base. Electron ships with Chrome’s content library and Node. So you can use all the latest features of web browsers and Node ecosystem. It got popular after many companies and open source devs started developing their apps with it.

In this tutorial, we’ll be building Hacker News cross-platform desktop app. We’ll be using HN official API. HN API is currently read-only so we’ll only be able to read and view stories and comments.

Setting up Development Environment

Since we will be using React and ES6, we need to transpile our code before using it in our electron app. After all, it’s Chromium which is responsible for frontend rendering and javascript execution. Just like browsers, electron can’t understand React’s JSX and CSS Preprocessors syntax. We’ll be using webpack to transform ES6 and JSX with babel along with all the necessary loaders required to transform SCSS into CSS.

Let’s start by creating a package.json file. Add some dependencies and boilerplate configuration to it.

We’ll be using three separate run commands to build and run our code. After scripts, we have separate configuration for Linux, Mac, and Windows. electron-builder will be using this configuration to generate distributable packages. In our devDependencies, all the babel-* packages will be used for ES6 and JSX transformation. We’re using concurrently package in our dev command to build code Webpack, run a development server and electron app. We’ll be using axios for network requests to HackerNews API.

Application Structure

The following structure will be used in our app.

Webpack Configuration

We’ll be using two separate configurations for building our app code. Development configuration will take care of transpilation, running webpack-dev-server and hot-reloading.

Our production config will take care of transpilation and output all necessary files to build folder for electron-builder.

Electron Configuration

Our electron main code resides in app/src/main/app.js. We’ll launch a BrowserWindow from this file and serve  app/src/main/index.html file. In createWindow() method, we are creating a new Instance of BrowserWindow

In index.html view, we’re bootstrapping our react app with dynamically generated style and script tags according to the environment. For the development environment, we’re using webpack-dev-server.js script serverd through webpack-dev-server. For the production environment, we’re serving webpack’s bundled js css scripts.

Bootstrapping React

Now we’ll start writing React code. In app/src/main/components/index.js  we’re rendering our application Root  component.

Our Root  component is responsible for rendering Sidebar  and Story WebView . I’ll explain each method from the constructor to network requests and render data to our children components.

We’re setting the initial state for our Root component in its  constructor(). We’ll be using all_stories  state for storing all top stories IDs from HN API. However, we won’t be rendering all of them to our sidebar component. Instead, we’ll only request first 20 stories from API and store them in loaded state. We’ll mark the story as active and update active_story  state when a user clicks on any story in the sidebar.

We’re invoking getStories()  method from componentDidMount()  lifecycle method which requests stories from HN API and update  all_stories state. After making sure the state is updated, we’re calling loadStories() method with starting and ending index.

In loadStories() method, we’re passing url_promises to axios.all()  method. By using all() method, we’re making sure that state updates after all requests are done. This way we’ll only have to update state once from multiple network calls. After that, we’re adding loaded stories to loaded state. After updating state, we’re marking the first story as active on initial render.

We’re marking stories as active when a user clicks on a sidebar story and unmark those that were marked previously.

In our render method, we’re rendering Sidebar and Story Components. We’re passing stories down to our Sidebar along with callbacks for loading more stories and loadStory. We’re passing url to our Story component which is responsible for rendering electron’s webview.

In our Sidebar component, we’re rendering divs with story title and registering onClick handlers for loading story in webview. We’re also registering an onClick handler for loading more stories at the end of our stories list.

In our Story component, we’re initially rendering a ScaleLoader. When the component receives a new url prop, we render a WebView with it after waiting 4 secs of loading. After applying few styles to our sidebar and story components, our app is ready for display.

Building and Distribution

By running npm run dev you’ll set up a dev environment. It will set up all the necessary tools required for you to build your react desktop app. By running npm run watch , you’ll be able to build and run your app without development tools. To generate a distributable package and installer, you need to run npm run dist. It will generate a distributable binary and installer for your application.

I’ve set up a GitHub example repository. You can clone and dive into code right away. If you’ve any questions or problems, leave a comment and i’ll try to help you.

Share this post

Leave a Reply

Your email address will not be published. Required fields are marked *