Building an Online IRR Calculator with React

January 23, 201911 min read

Last reviewed January 23, 2019

In the last article we covered the concepts of NPV (Net Present Value) and IRR (Internal Return Rate), with math insights and almost no code. Now we are going to get more serious and develop an online tool that will enable us to calculate the IRR for a given investment.

Curious already? Check it out.

Recap

Recall that IRR is the discount rate rr that gives us NPV=0NPV = 0. In simple words, for a given initial investment and a projected cash flow over time, IRR tells us what the return rate is.

Our goal then is to compare multiple investments and analyze their IRR’s in order to make better financial choices. It applies for a wide range of endeavours — from opening a new restaurant to buying AAPL stocks. The online calculator should be, then, simple to use and quick to test multiple assumptions. Let’s assess our requirements.

Requirements

Here’s a really condensed list of requirements for our app:

  1. It must work online and in multiple devices (responsive);
  2. The user must provide an initial amount, a period of time, and a cash flow;
  3. The user may input the cash flow manually or;
  4. The user may generate the cash flow automatically given a projected base cash flow and a growth rate;
  5. It must be fully accessible through the keyboard;
  6. The app must not accept invalid inputs.

As you can see, nothing fancy. For Requirement #4 we will need some extra math and for #5 we will need to deal with accessibility, DOM and refs.

The App

All the code for this simulator can be found on GitHub.

Here’s a simulation for one of the examples given in Making Better Investments with Math and JavaScript (Elon’s proposal). Compare it with the above requirements.

IRR Example

Requirements #3 and #4 are covered and #6 is gracefully achieved (button remains disabled until all inputs are present).

Let’s start with the code (don’t worry if you can’t figure out what all lines are doing, we will revisit them soon):

import React from 'react';
import css from './index.module.css';
import { IRR } from './util';
import NumericInput from './NumericInput';
import ModeSelector from './ModeSelector';
import Result from './Result';

const MODE = { AUTO: "AUTO", MANUAL: "MANUAL" };

export default class Calculator extends React.Component {

  constructor() {
    super();
    this.state = {
      initialAmount: "",
      projectedCash: "",
      growthRate: "",
      period: "",
      IRR: "",
      manualProjectedFlow: [],
      projectionMethod: MODE.AUTO
    };
    this.investmentInput = React.createRef();
  }

  ...

  render() {
    return (
      <div className={css['container']}>
        <header>
          <h1>IRR Calculator</h1>
        </header>
        <main className={css['main']}>
          <NumericInput 
            autoFocus
            min={0}
            label="Initial Investment"
            hint="Money you need to invest upfront"
            value={this.state.initialAmount}
            onChange={this.onInputChange.bind(this, "initialAmount")}
            ref={this.investmentInput}
          />
          <NumericInput 
            min={1}
            label="Period of Time"
            hint="Total period of investment. Should be between 1 and 30"
            value={this.state.period}
            onChange={this.onPeriodChange.bind(this)}  
          />
          <ModeSelector
            autoValue={MODE.AUTO}
            manualValue={MODE.MANUAL}
            isAutoMode={this.isAutoMode}
            isManualMode={this.isManualMode}
            onChange={e => this.setState({projectionMethod: e.target.value})}
          />
          {this.automaticProjection}
          {this.manualProjection}
          <button 
            disabled={!this.isFormValid} 
            onClick={this.onClick.bind(this)}>Calculate</button>
          {this.IRR}
        </main>
        <footer className={css['footer']}>
          Created by 
          {' '}
          <a href="https://rafaelquintanilha.com">Rafael Quintanilha</a>
        </footer>
      </div>
    );
  }
}

The constructor should be easy to follow. Note that we define our state as a list of empty values, the exception being manualProjectedFlow and projectionMode.

The first is an array which will hold the projected cash flow for a given period tt. So, in the above .gif, manualProjectedFlow is an array of length 1 in which manualProjectedFlow[0] === 11000. The latter determines in which mode we are: AUTO or MANUAL (defaults to AUTO).

We’ll talk about this.investmentInput = React.createRef(); in a bit. But first note that apart from the mode selection (MANUAL or AUTO) all inputs are numeric. It made sense then to come up with a <NumericInput /> component that we will describe next:

import React from 'react'
import css from './NumericInput.module.css';
import { uniqueId } from 'lodash';

export default class NumericInput extends React.Component {

  constructor() {
    super();
    this.id = uniqueId("irr-");
    this.input = React.createRef();
  }

  get hint() {
    if ( !this.props.hint ) return null;
    return <div className={css['hint']}>{this.props.hint}</div>;
  }

  focus() {
    this.input.current.focus();
  }

  render() {
    const { label, value, onChange, hint, ...rest } = this.props;
    return (
      <div>
        <label htmlFor={this.id}>{label}</label>
        <br />
        <input
          className={css['input']}
          ref={this.input}
          id={this.id}
          type="number" 
          value={value}
          onChange={onChange}
          {...rest}
        />
        {this.hint}
      </div>
    );
  }
}

Couple things going on here.

First and foremost note that we use destructure assignment in order to flexbilize the accepted props for our input. We only fix some props, notably type, value and onChange.

Notice also that, in order to meet a11y standards, we generate an id by calling lodash’s uniqueId. We then assign this id to both input and label, so screenreaders now can work properly.

Finally notice that we create a ref and assign it to our input. More than that, we create a class method focus() which basically focus on the input. Why is that? Recall one of the NumericInput components of Calculator:

<NumericInput 
  autoFocus
  min={0}
  label