Building an Expense Application with Electron and React

“JavaScript is everywhere these days. You can even use it to develop desktop applications. Platforms, such as NW.js (formerly node-webkit) and Electron (formerly known as Atom Shell) enable this. In this post I am going to show you how to develop a small expense tracking application on top of Electron and React.

Getting Started with Electron

Before going further, make sure you have a fresh version of Node.js and git installed.

We can get a rough Electron application running really quick:

git clone https://github.com/chentsulin/electron-react-boilerplate.git expense-app
cd expense-app
npm install

After these steps we’ll need to run the following commands in two separate terminals:

npm run hot-server
npm run start-hot

Once you have run the latter, you should see something like this:

As you might guess by now, we actually have a browser running now. To be exact, it’s a version of Chromium. Compared to normal web development, though, we have more power at our disposal. We can access the file system or use desktop notifications for example. We can even bundle our application and push it to Mac App Store should we want to.

Expense Application Design

To track expenses, we are going to need three basic concepts:

  • Income — A list of income names and the amount of each.
  • Expense — A list of expense names and the cost per each.
  • Total — The sum of losses substracted from the sum of profits.

In terms of user interface we are going to need some way to enter incomes and expenses. In addition we need to visualize them, and total, somehow. The first problem can be solved through a dropdown (income/expense) and an input. When you hit enter, it should add the value to the chosen collection. The latter can be solved through list based visualization (name, amount) and an element to display the calculated total.

Implementing the Application

To get started with the implementation, we could model a control for adding incomes and expenses. For the sake of simplicity, the implementation will track just the type of value and the amount/cost.

app/components/Home.js

import React, { Component } from 'react';
import styles from './Home.css';
export default class Home extends Component {
render() {
return (
<div>
<div className={styles.container}>
<div className={styles.addValue}>
<select>
<option value="income">Income</option>
<option value="expense">Expense</option>
</select>
<input type="number" min="0" />
</div>
<button type="button">Add</button>
</div>
</div>
);
}
}

app/components/Home.css

.container {
position: absolute;
top: 30%;
left: 10px;
text-align: center;
}
.container h2 {
font-size: 5rem;
}
.container a {
font-size: 1.4rem;
}
.addValue {
display: inline;
}

After updating the code, you should see this:

We still need to capture the user input somehow and render the values.

Even though the boilerplate provides Redux, I am going to keep it simple and use React’s state instead. Due to this you will need to force refresh after the next addition! So trigger either cmd-r or ctrl-r at Electron window after changing the code as follows.

import React, { Component } from 'react';
import styles from './Home.css';
export default class Home extends Component {
constructor(props) {
super(props);
    this.state = {
income: [],
expense: []
};
    this.addValue = this.addValue.bind(this);
}
render() {
const sum = (a, b) => a + b;
const income = this.state.income;
const expense = this.state.expense;
const total = income.reduce(sum, 0) - expense.reduce(sum, 0);
    return (
<div>
<div className={styles.container}>
<div className={styles.addValue}>
<select ref="valueType">
<option value="income">Income</option>
<option value="expense">Expense</option>
</select>
<input type="number" min="0" ref="value" />
</div>
<button type="button" onClick={this.addValue}>Add</button>
</div>
        <div>Income</div>
<Values values={this.state.income} />
        <div>Expense</div>
<Values values={this.state.expense} />
        <div>Total: {total}</div>
</div>
);
}
addValue() {
const valueType = this.refs.valueType.value;
    // It would be a good idea to validate the value here!
const value = parseInt(this.refs.value.value, 10);
    this.setState({
[valueType]: this.state[valueType].concat(value)
});
}
}
const Values = ({values}) => {
return (
<ul>
{values.map((value, i) =>
<li key={`value-${i}`}>{value}</li>
)}
</ul>
);
}

There is actually a lot going on here. If you haven’t seen React code before, a lot of it might seem alien. We are leveraging a couple of core concepts of React here:

  • this.state — This refers to the internal state of the component in question. An interesting alternative would be to push it out of the component altogether, but that’s beyond the scope of this post.
  • render() — That’s where we can decide how to display the data. We derive the value of totaldynamically in addition to rendering our values through a custom component known as Values.Values relies on a function based component definition and it’s literally just render() by itself.
  • const valueType = this.refs.valueType.value; — We extract the value of our selectelement through a React ref. Refs give us direct access to the DOM and provide an escape hatch of sort. This is known as the uncontrolled way to treat form fields. Alternatively we could capture the state through event handlers and control the value within React. Now our implementation is tied to DOM and changing the implementation would break this dependency.
  • {values.map((value, i) => … } — The brace syntax allows us to mix JavaScript with JSX. JSX syntax itself is a light syntactical wrapper on top of React’s JavaScript API. We are relying on it heavily in this example.
  • <li key={value-${i}}>{value}</li> — To help React tell different list items apart, we are setting key here. Setting it based on an array index like this isn’t the ideal solution, but it’s enough for this demo. It would be a better idea to generate unique ids per each in our data model.

Assuming everything went fine, you should see something like this after using the application for a while:

Conclusion

The current application is somewhat simple when it comes to functionality and it’s far from a production grade one. It would be fun to add more features to it:

  • Consider adding an input for entering income/expense name. Doing this change would mean that you would have to change the data model and start operating using arrays of objects instead of arrays of numbers.
  • The outlook of the application could be improved somewhat as well.
  • You could look into leveraging Redux over React state.
  • Explore Electron’s capabilities further and save the data to the file system. You could also play around with notifications and show them when some limit is reached for instance.

To learn more about the topic, consider checking out my book, SurviveJS. It’s freely available and delves far deeper into the topic.”

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.