React Daily UI - 004 Calculator

Edit this page on Github

This post is a part of the React Daily UI post series, a joint effort between Jack Oliver, Sophia Shoemaker, and the rest of the team at Fullstack React. Each day we're explaining in detail how to create a UI component with React.

You can view the Codepen implementation here

Or you view the code on Github here

Welcome to React Daily UI, where we go 100 days building 100 beautiful React applications. We're really excited to be partnering with Jack Oliver who is embarking on this ambitious project with us.

Jack is designing and writing the code for these applications and we're going to deconstruct each one to highlight the features that are unique to React.

Today we're going to create a calculator:

Overview

Today we are going to make a calculator. We're going to use a few additional libraries, mathjs,node-sass, nodemon and concurrently to build our calculator. Instead of using the npm client, we are going to use the new Yarn package manager to include these libraries. We'll also learn about immutability and explore how to use Sass with the create-react-app command line tool.

Table of Contents

Using Yarn instead of npm

Yarn is a new package manager built through a colloborative effort from engineers at Facebook, Google, Tilde and Exponent. It is a replacement for the npm client that many JavaScript developers have been using to manage dependencies between various JavaScript libraries. It does not replace the npm registry, only the npm command line tool used to manage dependencies. If you have used the npm command line tool to add/remove dependencies, using Yarn will feel very familiar.

The engineers at Facebook found they were stretching the limits of what the npm client could do. Running npm install was not a predicatable process across all machines and operating systems and updating a single dependency was often larger than expected due to other dependencies getting updated at the same time. Yarn's implementation solves these issues along with a few other pain points engineers encountered. If you'd like to read more details about Yarn, read the announcement on Facebook's blog. In this blog post and future blog posts we'll be using Yarn when we need to install an extra library. There are a variety of ways to install Yarn (see the docs here: https://yarnpkg.com/en/docs/install), but since we already have npm and Node installed, we'll use npm:

npm install -g yarn

Making Calculations

Our calculator does some simple operations: addition, subtraction, division and multiplaction. To keep track of which button the user clicked, we are going to keep an array of all the button values. We are going to store this array in our App component's state property.

What is state?

When we refer to a component's state, we mean a snapshot of the data belonging to a particular instance of the component. React's components can define their own state which we'll use in today's post, and others in the future. Using the state property allows us to manipulate a React component's view and data associated with the view to keep track of the local state of the component.

We set the initial state of our App component by defining a method called getInitialState. React expects us to return a JavaScript object from this method that stores any sort of data we want to manipulate or display in the component.

Let's tell React that the App component keeps an item in it's local state, an array we'll call operations:


getInitialState: function() {
  return ({
    operations: []
  });
},

We have multiple buttons in our application, one for each number and then one for each operation: addition (+), subtraction (-), multiplication (x), and division (\). We also have an equals button (=) and a clear button (C). Each button has an onClick event handler function attached to it:


<Buttons>
  <Button onClick={this.handleClick} label="C" value="clear" />
  <Button onClick={this.handleClick} label="7" value="7" />
  <Button onClick={this.handleClick} label="4" value="4" />
  <Button onClick={this.handleClick} label="1" value="1" />
  <Button onClick={this.handleClick} label="0" value="0" />
  
  <Button onClick={this.handleClick} label="/" value="/" />
  <Button onClick={this.handleClick} label="8" value="8" />
  <Button onClick={this.handleClick} label="5" value="5" />
  <Button onClick={this.handleClick} label="2" value="2" />
  <Button onClick={this.handleClick} label="." value="." />
  
  <Button onClick={this.handleClick} label="x" value="*" />
  <Button onClick={this.handleClick} label="9" value="9" />
  <Button onClick={this.handleClick} label="6" value="6" />
  <Button onClick={this.handleClick} label="3" value="3" />
  <Button label="" value="null" />
  
  <Button onClick={this.handleClick} label="-" value="-" />
  <Button onClick={this.handleClick} label="+" size="2" value="+" />
  <Button onClick={this.handleClick} label="=" size="2" value="equal" />
</Buttons>

The handleClick function is called when the user clicks any button. To determine which button the user pushed, our code has a switch statement. When the user clicks any button except for the = and C we add the value of the button to our operations array:


default:
  var newOperations = update(this.state.operations, {$push: [value]});
  this.setState({operations: newOperations});
  break;

We don't want to use the regular push method for JavaScript arrays to add our values to this.state.operations because that modifies the original array. JavaScript arrays are mutable and we want our this.state.operations array to be immutable.

What is immutability?

When we use the term immutability (or say something is immutable) we mean an object (or in our case an array) that cannot be changed once it is created. Arrays and objects are mutable - you can add values to the original array or object. But we don't want to mutate our objects because it can cause consistency problems down the line.

Interestingly, in JavaScript strings and numbers are immutable, once a string is created, it cannot be altered. Similarly a number itself cannot be changed (e.g. 42 is always 42 and never 43). This is somewhat counter-intuitive in that we append to strings or increment numbers. But what's happening when we append/increment is not that the string/number itself is changing but rather we're changing the value of the variable we're assigning that value to.

Rather than modifying this.state.operations, we want to return a new array that has the new value in it. React has an addon library update that allows us to create a new array or object every time we want to add a value to our object or array. We'll use this update function modify this.state.operations:


var newOperations = update(this.state.operations, {$push: [value]});

This line of code says take this.state.operations and "push" (add to) that array the value. The $push syntax is interpreted as an operation to the update() function. You can read more about these immutability helpers here.

Once we've modified our operations array, we'll call this.setState to tell React our component has changed and the DOM needs updating:


this.setState({operations: newOperations});

By using immutable arrays and objects in our React applications we can optimize performance and improve reliability. You can rely on data in a given object and know it will not change somewhere else in your application (and cause unintended consequences). While it's hard to see in a small application like this the performance and reliability benefits, immutability is an important concept to learn because the bigger the application is, the more performance and reliability matter. In future blog posts we will go into more detail about how to optimize performance with React using immutable data structures.

When the user clicks the = button we need to evaluate the expression to get the final value. We could use the global eval function to evaluate the string, but this is prone to security holes since it will evaluate any JavaScript expression, not just math expressions. In the future, if we want to expand our calculator to do more than just addition, subtraction, multiplication and division, it will be beneficial to use a library that allows us to go beyond those operations. We will use the mathjs library to evaluate our expression.

mathjs includes its own eval function which only parses mathematical expressions and so it doesn't suffer from the same security pitfalls as using the global eval. We'll use math.eval() to calculate the result of our calculator inputs.

To use mathjs, we'll install it via Yarn. To include a library with Yarn we run this command:

yarn add mathjs

To use the library we import it into our file:


import math from 'mathjs'

When the user clicks the = button we call the calculateOperations method. In this method we combine all the entries in the array into a string using the join method:


var result = this.state.operations.join('');

We use the mathjs eval function to evaluate the expression and wrap the expression back into a string so it will display properly:


result = String(math.eval(result));

Finally, we call this.setState to update our application:


this.setState({ operations : [result] });

If the user clicks on the C button, we want to clear everything out of our operations array and update our component using this.setState:


this.setState({ operations: []});

To actually display the values in the this.state.operations array we have a Display component. It takes the this.state.operations array as a prop named data. Every time this.setState is called and our operations array has changed, our Display component will change as well:


<Display data={this.state.operations} />

In our Display component we take all the values from this.state.operations and concatenate them into a string using the join method. Our render method returns the string wrapped in a div:


render: function() {
  var string = this.props.data.join('');
  return (
    <div className="Display">
      {string}
    </div>
  );
}

Sass with create-react-app

For each one of our React Daily UI projects, we've been using the create-react-app command line tool to get started. create-react-app is the official tool from Facebook for starting a new React application. At this point in time, the tool doesn't allow for any customizations. Using Sass with the create-react-app requires a little extra work on our part. There are a few different options we can use to combine Sass and create-react-app.

Option 1: npm run eject

If you have a highly customized and complicated build process for your Sass/CSS files and are looking to start a new project with React using your existing build process, it is probably beneficial to run the npm run eject command. This command will copy all the configuration files and into your project so you have full control over them. You will lose any future updates to the create-react-app tool and it's associated scripts, but you'll also have full control over configuring your application just the way you want.

Option 2: Run a Sass watcher in the background

Another option is to run a Sass compiler and watch for the changes in a background script by running a command like this:

node-sass --watch --recursive src & react-scripts start

The drawback to this method is that if you kill your process, the background process will still be running. You'll have to use the ps command to find the background process and kill it manually.

Option 3: Use the concurrently library

concurrently is a library that allows you to run multiple processes at the same time without the hassle of creating a background process.

Let's first set up a Sass file watcher that will compile our .scss files into .css files.

We'll use two libraries: node-sass and nodemon. node-sass is a library that provides Node.js bindings to LibSass, the C version of a Sass precompiler. nodemeon is a file watcher that will reload the server when any file changes on the system.

All of our styles are stored in the src/App.scss file. To compile our Sass files using node-sass we'll run this command:

node-sass --include-path scss src/App.scss src/App.css

We'll add this command to our package.json file in the "scripts" section below the eject command:

"build-css": "node-sass --include-path scss src/App.scss src/App.css",

To use nodemon to watch our Sass files we'll add another script in our package.json file:

"watch-css": "nodemon -e scss -x \"npm run build-css\"",

The -e flag tells nodemon to watch files with the .scss extension and the -x flag tells nodemon to execute the script specified, in this case it will compile all our Sass files to CSS.

Now, in order to add a Sass file watcher and run the npm start command at the same time let's first add the concurrently library using Yarn:

yarn add concurrently

Then we add another command: start-with-sass to our package.json file. We combine our watch-css command with npm start (the command that starts the development server):

"start-with-sass": "concurrently --kill-others \"npm run watch-css\" \"npm start\""

The --kill-others flag says kill all the scripts that are running at the same time.

Try it out!

Check out the Codepen example:

The complete source for this article is also available on Github here.

To start the app, download the code, cd into the project directory and type:

   npm install
   npm start

Sophia Shoemaker

Sophia Shoemaker became addicted to React in 2014. She is a full stack developer but React is her technology of choice. She loves working on her pet project Shop Sifter and editing the Fullstack React Newsletter.

Recently, she became a mentor at Hackbright Academy and is excited to encourage and empower women looking to get involved in programming. When she isn't cobbling code, she can be found flying in her husband's Beechcraft Bonanza and playing with her 2 kids.

Connect with Sophia on Twitter at @wisecobbler.

Jack Oliver

Hi, I'm Jack! I'm a Developer & Designer living in Stockholm, Sweden. I've worked with super cool people; from Mercedes-Benz, Farfetch, NotOnTheHighStreet, and Mimecast, and am currently building cool stuff at Observify. Part-time photographer, full-time joker. I'm currently doing 100 days of React on Codepen check it out here.

Connect with me on twitter @mrjackolai.