How To Make Income Tracker In React

In this article, we will create an income tracker in react. Here we have some fields where user can write their income source, income and date. As user fill these, user clicks on button and data will be entered on list, and we update the total income. It is a very basic project to learn, not too much fancy but it will be good enough to make it.

This project won’t be too hard to do, but it will be very simple and easy to do. So let’s make this application step-by-step.

Pre-requisites to Make Income Tracker in React

  • Basic knowledge of ReactJS.
  • Basic knowledge of CSS.
  • Basic knowledge of React props and hooks.
  • Good knowledge of React Components.

Customizing App Component

Firstly, we will make some changes in App.js component. Here we have imported our required components like React, Header, IncomeForm, IncomeList and hooks which are going to help us to make this project. We will define Header, IncomeForm, IncomeList components as we progress in this project.

Now in App component, we have created two states, one for income and the other for total income with initial values null and 0 respectively. After that in return, we have made an App <div>, in which we have added all component with props. Here we have passed states as props because these values are going to change as user input them, and we can work with updated values.

import React, { useState, useEffect } from 'react';
import Header from './components/Header';
import IncomeForm from './components/IncomeForm';
import IncomeList from './components/IncomeList';

function App() {
	const [income, setIncome] = useState([]);
	const [totalIncome, setTotalIncome] = useState(0);
    
    return (
		<div className="App">
			<Header totalIncome={totalIncome} />
			<IncomeForm income={income} setIncome={setIncome} />
			<IncomeList income={income} setIncome={setIncome} />
		</div>
	);
}

export default App;

Creating Header Component

Now we will create heading component which is actually we already added in App.js, we just need to define this. So In this we have created a <header>, in which we have added a heading with a text, and then we added a div for total income with totalIncome prop which is passed from App.js component.

Header.js

import React from 'react';

function Header({totalIncome}) {
  return (
    <header>
      <h1>Income Tracker</h1>
      <div className="total-income">£{totalIncome}</div>
    </header>
  );
}

export default Header;
Income tracker in react

Creating Form To Get Inputs

Now, after creating the header, we will create a form to get input from user. For that, we have here, imported useRef hook to get the input. Then we have added 3 constants with null values using useRef.

After that, let’s move to return(), here we have added a form with onSubmit where we have passed a function. Now in this form, we have added 3 inputs for like, description, income and date. Lastly, we have added another input for submit type.

Now in AddIncome, we have used the e.preventDefault() method to erase default values. Then we have added a variable in which we have split the date to get date values like day, month, and year. We used date.current.value.split("-") to get day, month and year in a form of array element.

Then we have created an object of Date with day, month and year which are stored in variable d as array. Now we will update value of income using setIncome state, and we can use here because we fetched these as props. Here we have updated all constants for description, price and date with entered value using current.value. Then we just again reset these values after the form gets submitted.

InputForm.js

import React, {useRef} from 'react';

function IncomeForm({ income, setIncome }) {
  const desc = useRef(null);
  const date = useRef(null);
  const price = useRef(null);

  const AddIncome = e => {
    e.preventDefault();

    let d = date.current.value.split("-");
    let newD = new Date(d[0], d[1] - 1, d[2]);
    
    setIncome([...income, {
      "desc": desc.current.value,
      "price": price.current.value,
      "date": newD.getTime()
    }]);

    desc.current.value = "";
    price.current.value = null;
    date.current.value = null;
  }

  return (
    <form className="income-form" onSubmit={AddIncome}>
      <div className="form-inner">
        <input type="text" name="desc" id="desc" placeholder="Income Description..." ref={desc} /> 
        <input type="number" name="price" id="price" placeholder="Price..." ref={price}/>
        <input type="date" name="date" id="date" placeholder="Income date..." ref={date} />
        <input type="submit" value="Add Income" />
      </div>
    </form>
  )
}

export default IncomeForm;
Income tracker in react

Creating Income Items

Now, we will create a component to add and remove items. For that we have added props income, index which are known to us but removeIncome is unknown, So this prop we will fetch from other component named IncomeList.js which we will define later on. But for now, assume it for removing any item. Now we have again added variables to store date month and year.

Then in return(), we have added a button with a function removeHandle, and in this function we have called another function which is our removeIncome. This thing we will define in the IncomeList component. Then we have multiple div for description, price and date.

IncomeItems.js

import React from 'react';

function IncomeItem({income, index, removeIncome}) {
  let date = new Date(income.date);
  let day = date.getDate();
  let month = date.getMonth() + 1;
  let year = date.getFullYear();

  const removeHandle = i => {
    removeIncome(i);
  }

  return (
    <div className="income-item">
      <button className="remove-item" onClick={() => removeHandle(index)}>x</button>
      <div className="desc">{income.desc}</div>
      <div className="price">£{income.price}</div>
      <div className="date">{day + "/" + month + "/" + year}</div>
    </div>
  )
}

export default IncomeItem;

Creating Income List

Now we are in our final stage and component, Here we have imported our IncomeItem component, so that we can make use of its functionality. Now, in the return statement, we have added a mapping method to sort the items by sort using this line of code income.sort(sortByDate).map((value, index). Here we have called IncomeItem to get key, income, index and remove button. If you are familiar with react components, then it would be easier to understand.

Then we have defined removeIncome function, in which we have filtered the income where index won’t be i. this means we will keep those items which don’t have index i. As we have passed i from IncomeItem component on which we have added removeItem function.

IncomeList.js

import React from 'react';
import IncomeItem from './IncomeItem';

function IncomeList({ income, setIncome }) {

  const removeIncome = i => {
    // eslint-disable-next-line eqeqeq
    let temp = income.filter((v, index) => index != i);
    setIncome(temp);
  }

  const sortByDate = (a, b) => {
    return a.date - b.date;
  }

  return (
    <div className="income-list">
      {
        income.sort(sortByDate).map((value, index) => (
          <IncomeItem 
            key={index} 
            income={value} 
            index={index} 
            removeIncome={removeIncome}
          />
        ))
      }
    </div>
  )
}

export default IncomeList;
Income tracker in react

Customization to The App

We have mainly focused on the React code, so we don’t get into the CSS part in deep. In CSS part, we just have added some basic styles to the app, which is purely depends on you to customize it. You can add below CSS in your project if you don’t wont to add much more things.

:root {
	--light: #F8F8F8;
	--dark: #313131;
	--grey: #888;
	--primary: #FFCE00;
	--secondary: #FE4880;
	--alert: #FF1E2D;
}

* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
	font-family: montserrat, sans-serif;
}

input, button {
	border: none;
	outline: none;
	background: none;
}

body {
	background-color: var(--light);
}

.App {
	padding: 30px;
}

header {
	display: flex;
	justify-content: space-between;
	padding: 15px;
}

h1 {
	color: var(--grey);
	font-size: 32px;
	font-weight: 600;
	text-align: left;
}

.total-income {
	color: var(--grey);
	font-size: 28px;
	font-weight: 900;
	background-color: #DFDFDF;
	padding: 5px 25px;
	border-radius: 8px;
}

.income-form {
	display: block;
	margin: 15px;
	position: relative;
}

.income-form:after {
	content: "";
	position: absolute;
	top: 0px;
	left: 0px;
	right: 0px;
	bottom: 0px;
	z-index: 0;
	background-image: linear-gradient(to right, var(--primary), var(--secondary));
	border-radius: 10px;
	transition: 0.2s;
}

.income-form:focus-within:after {
	top: -3px;
	left: -3px;
	right: -3px;
	bottom: -3px;
}
.form-inner {
	position: relative;
	z-index: 1;
	display: flex;
	justify-content: center;
	transition: 0.4s;
	border-radius: 8px;
}
.form-inner input {
	font-size: 18px;
	padding: 10px 15px;
	background-color: #FFF;
}

.form-inner input[type="text"] {
	border-radius: 8px 0px 0px 8px;
	flex: 1 1 100%;
}
.form-inner input[type="date"] {
	border-radius: 0px;
	min-width: 200px;
}
.form-inner input[type="submit"] {
	border-radius: 0px 8px 8px 0px;
	cursor: pointer;
	
	background-image: linear-gradient(to right, var(--primary) 50%, var(--primary) 50%, var(--secondary));
	background-size: 200%;
	background-position: 0%;

	color: var(--dark);
	font-weight: 600;
	text-transform: uppercase;

	transition: 0.4s;
}

.form-inner input[type="submit"]:hover {
	background-position: 100%;
	color: #FFF;
}

.income-list {
	padding: 15px;
}

.income-list .income-item {
	position: relative;
	padding: 10px 15px;
	background-color: #FFF;
	border-radius: 8px;
	margin-bottom: 15px;
	display: flex;
	transition: 0.4s;
	padding-left: 75px;
}

.income-list .income-item .remove-item {
	position: absolute;
	width: 0;
	top: 0;
	left: 0;
	bottom: 0;
	overflow: hidden;

	cursor: pointer;
	display: block;
	color: var(--light);
	font-weight: 600;
	background-color: var(--alert);
	border-radius: 8px 0px 0px 8px;
	transition: 0.2s;
}

.income-list .income-item:hover .remove-item {
	width: 50px;
}

.income-list .income-item .desc {
	flex: 1 1 100%;
}

.income-list .income-item .price,
.income-list .income-item .date {
	min-width: 125px;
}
.income-list .income-item:hover {
	box-shadow: 0px 0px 6px rgba(0,0,0,0.1);
}

Full Source Code to Make Income Tracker in React

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

App.js

import React, { useState, useEffect } from 'react';
import Header from './components/Header';
import IncomeForm from './components/IncomeForm';
import IncomeList from './components/IncomeList';

function App() {
	const [income, setIncome] = useState([]);
	const [totalIncome, setTotalIncome] = useState(0);

	useEffect(() => {
		let temp = 0;
		for(let i = 0; i < income.length; i++) {
			temp += parseInt(income[i].price);
		}

		setTotalIncome(temp);
	}, [income]);
	

	return (
		<div className="App">
			<Header totalIncome={totalIncome} />
			<IncomeForm income={income} setIncome={setIncome} />
			<IncomeList income={income} setIncome={setIncome} />
		</div>
	);
}

export default App;

Header.js

import React from 'react';

function Header({totalIncome}) {
  return (
    <header>
      <h1>Income Tracker</h1>
      <div className="total-income">£{totalIncome}</div>
    </header>
  );
}

export default Header;

IncomeForm.js

import React, {useRef} from 'react';

function IncomeForm({ income, setIncome }) {
  const desc = useRef(null);
  const date = useRef(null);
  const price = useRef(null);

  const AddIncome = e => {
    e.preventDefault();

    let d = date.current.value.split("-");
    let newD = new Date(d[0], d[1] - 1, d[2]);
    
    setIncome([...income, {
      "desc": desc.current.value,
      "price": price.current.value,
      "date": newD.getTime()
    }]);

    desc.current.value = "";
    price.current.value = null;
    date.current.value = null;
  }

  return (
    <form className="income-form" onSubmit={AddIncome}>
      <div className="form-inner">
        <input type="text" name="desc" id="desc" placeholder="Income Description..." ref={desc} /> 
        <input type="number" name="price" id="price" placeholder="Price..." ref={price}/>
        <input type="date" name="date" id="date" placeholder="Income date..." ref={date} />
        <input type="submit" value="Add Income" />
      </div>
    </form>
  )
}

export default IncomeForm;

IncomeItems.js

import React from 'react';

function IncomeItem({income, index, removeIncome}) {
  let date = new Date(income.date);
  let day = date.getDate();
  let month = date.getMonth() + 1;
  let year = date.getFullYear();

  const removeHandle = i => {
    removeIncome(i);
  }

  return (
    <div className="income-item">
      <button className="remove-item" onClick={() => removeHandle(index)}>x</button>
      <div className="desc">{income.desc}</div>
      <div className="price">£{income.price}</div>
      <div className="date">{day + "/" + month + "/" + year}</div>
    </div>
  )
}

export default IncomeItem;

IncomeList.js

import React from 'react';
import IncomeItem from './IncomeItem';

function IncomeList({ income, setIncome }) {

  const removeIncome = i => {
    // eslint-disable-next-line eqeqeq
    let temp = income.filter((v, index) => index != i);
    setIncome(temp);
  }

  const sortByDate = (a, b) => {
    return a.date - b.date;
  }

  return (
    <div className="income-list">
      {
        income.sort(sortByDate).map((value, index) => (
          <IncomeItem 
            key={index} 
            income={value} 
            index={index} 
            removeIncome={removeIncome}
          />
        ))
      }
    </div>
  )
}

export default IncomeList;

Index.css

:root {
	--light: #F8F8F8;
	--dark: #313131;
	--grey: #888;
	--primary: #FFCE00;
	--secondary: #FE4880;
	--alert: #FF1E2D;
}

* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
	font-family: montserrat, sans-serif;
}

input, button {
	border: none;
	outline: none;
	background: none;
}

body {
	background-color: var(--light);
}

.App {
	padding: 30px;
}

header {
	display: flex;
	justify-content: space-between;
	padding: 15px;
}

h1 {
	color: var(--grey);
	font-size: 32px;
	font-weight: 600;
	text-align: left;
}

.total-income {
	color: var(--grey);
	font-size: 28px;
	font-weight: 900;
	background-color: #DFDFDF;
	padding: 5px 25px;
	border-radius: 8px;
}

.income-form {
	display: block;
	margin: 15px;
	position: relative;
}

.income-form:after {
	content: "";
	position: absolute;
	top: 0px;
	left: 0px;
	right: 0px;
	bottom: 0px;
	z-index: 0;
	background-image: linear-gradient(to right, var(--primary), var(--secondary));
	border-radius: 10px;
	transition: 0.2s;
}

.income-form:focus-within:after {
	top: -3px;
	left: -3px;
	right: -3px;
	bottom: -3px;
}
.form-inner {
	position: relative;
	z-index: 1;
	display: flex;
	justify-content: center;
	transition: 0.4s;
	border-radius: 8px;
}
.form-inner input {
	font-size: 18px;
	padding: 10px 15px;
	background-color: #FFF;
}

.form-inner input[type="text"] {
	border-radius: 8px 0px 0px 8px;
	flex: 1 1 100%;
}
.form-inner input[type="date"] {
	border-radius: 0px;
	min-width: 200px;
}
.form-inner input[type="submit"] {
	border-radius: 0px 8px 8px 0px;
	cursor: pointer;
	
	background-image: linear-gradient(to right, var(--primary) 50%, var(--primary) 50%, var(--secondary));
	background-size: 200%;
	background-position: 0%;

	color: var(--dark);
	font-weight: 600;
	text-transform: uppercase;

	transition: 0.4s;
}

.form-inner input[type="submit"]:hover {
	background-position: 100%;
	color: #FFF;
}

.income-list {
	padding: 15px;
}

.income-list .income-item {
	position: relative;
	padding: 10px 15px;
	background-color: #FFF;
	border-radius: 8px;
	margin-bottom: 15px;
	display: flex;
	transition: 0.4s;
	padding-left: 75px;
}

.income-list .income-item .remove-item {
	position: absolute;
	width: 0;
	top: 0;
	left: 0;
	bottom: 0;
	overflow: hidden;

	cursor: pointer;
	display: block;
	color: var(--light);
	font-weight: 600;
	background-color: var(--alert);
	border-radius: 8px 0px 0px 8px;
	transition: 0.2s;
}

.income-list .income-item:hover .remove-item {
	width: 50px;
}

.income-list .income-item .desc {
	flex: 1 1 100%;
}

.income-list .income-item .price,
.income-list .income-item .date {
	min-width: 125px;
}
.income-list .income-item:hover {
	box-shadow: 0px 0px 6px rgba(0,0,0,0.1);
}

Output

Income tracker in react

Check out video reference here:

You may also like:

Reactjs Guru
Reactjs Guru

Welcome to React Guru, your ultimate destination for all things React.js! Whether you're a beginner taking your first steps or an experienced developer seeking advanced insights.

React tips & tutorials delivered to your inbox

Don't miss out on the latest insights, tutorials, and updates from the world of ReactJs! Our newsletter delivers valuable content directly to your inbox, keeping you informed and inspired.

Leave a Reply

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

Table of Contents