Learn React by building a Love Calculator App❤

Subramanya M RaoSubramanya M Rao
12 min read

If you love to understand concepts by building a project or are into project-based learning, this is the best tutorial for you.

This is a cool lil project one can build with basic concepts of React JS.


Steps involved📝

  1. Create a new React App.

  2. Build a form with two inputs to accept names.

  3. Send names to our API.

  4. Retrieve results and display them to the user.


Creating a React App⚛️

You can create a new react app in various ways like using CRA, Vite, Parcel, and Using meta frameworks like Next JS, Remix, and so on. However, we will be using Vite. Along with Vite, I've used tailwind CSS to style this project.

You can bootstrap a new react project with Tailwind CSS by following these Guide @tailwind docs

Building the App🛠

Now that we have a blank react app, create a new directory in the src directory called components which will have all of our components. src/components . It is not mandatory, but it is a good and well-renowned practice that is widely used by react developers.

In our app, we will have mainly 3 components a Navbar component (which is optional), a Calculator component (which wraps up our entire app), and a Progress component which we use further.

So Initially we will create these three components in our src/components directory & import these into our App.jsx as follows:-

import React from 'react'
import Navbar from './components/Navbar'
import Calculator from './components/Calculator'

export default function App() {

  return (
    <>
    <Navbar />
    <Calculator/>
    </>
  )
}

Let's actually implement these components, I am using react with javascript but you can opt for typescript as well.

With that being set we can start implementing our custom Navbar component. As said earlier this is optional, But since this is a small app with basic functionality I thought of adding this navbar.

import React from 'react'

function Navbar() {
  return (
    <header className="absolute top-0 w-full flex-none border-b border-slate-900/10 bg-white">
    <div className="mx-auto max-w-6xl">
      <div className="mx-4 my-2">
        <div className="relative flex items-center justify-between">
          <div className="flex flex-row items-center justify-center space-x-3">
            <p className="text-xl font-bold text-pink-600 hover:text-pink-500 cursor-pointer">
              Love Calculator
            </p>
          </div>

          <a
            href="https://github.com/Subramanyarao11/Love-Calculator"
            target="_blank"
            rel="noreferrer"
          >
            <div className="relative group">
              <div className="absolute -inset-0.5 bg-gradient-to-r from-pink-600 to-purple-600 rounded-full "></div>
              <div className="absolute -inset-0.5 bg-gradient-to-r from-pink-600 to-purple-600 rounded-full group-hover:blur group-hover:opacity-75 group-hover:transition group-hover:duration-200"></div>
              <button className="relative px-4 py-2 bg-black rounded-full leading-none flex items-center">
                <span className="flex items-center space-x-3">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    strokeWidth="1.5"
                    stroke="white"
                    className="w-6 h-6"
                  >
                    <path
                      strokeLinecap="round"
                      strokeLinejoin="round"
                      d="M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z"
                    />
                  </svg>

                  <span className="text-gray-100">Star on Github</span>
                </span>
              </button>
            </div>
          </a>
        </div>
      </div>
    </div>
  </header>
  )
}

export default Navbar

This is a simple navbar component with our app's title and a custom button with GitHub SVG wrapped within a flexbox. As said in the beginning we have used tailwind to style our app. You can use any kind of Styling.

Now that we have implemented Navbar, let's build the heart of our app which is the Calculator component. I've named it a calculator, you can call it anything.

As said we want to accept two names from the user as input, pass these names as parameters to our API, make a GET request to our API, retrieve results, and Display results to our user. Let's implement the calculator component.

First, within our calculator component, we will have a title within the <h1> tag that says Calculate love compatibility as follows

{/* Custom Title */

                <h1 className="pt-2 mx-auto text-center  max-w-4xl font-display text-4xl font-medium tracking-tight text-slate-900 sm:text-5xl">
                    Calculate{' '}
                    <span className="relative whitespace-nowrap text-pink-600">
                        <svg
                            aria-hidden="true"
                            viewBox="0 0 418 42"
                            className="absolute top-2/3 left-0 h-[0.58em] w-full fill-pink-300/70"
                            preserveAspectRatio="none"
                        >
                            <path d="M203.371.916c-26.013-2.078-76.686 1.963-124.73 9.946L67.3 12.749C35.421 18.062 18.2 21.766 6.004 25.934 1.244 27.561.828 27.778.874 28.61c.07 1.214.828 1.121 9.595-1.176 9.072-2.377 17.15-3.92 39.246-7.496C123.565 7.986 157.869 4.492 195.942 5.046c7.461.108 19.25 1.696 19.17 2.582-.107 1.183-7.874 4.31-25.75 10.366-21.992 7.45-35.43 12.534-36.701 13.884-2.173 2.308-.202 4.407 4.442 4.734 2.654.187 3.263.157 15.593-.78 35.401-2.686 57.944-3.488 88.365-3.143 46.327.526 75.721 2.23 130.788 7.584 19.787 1.924 20.814 1.98 24.557 1.332l.066-.011c1.201-.203 1.53-1.825.399-2.335-2.911-1.31-4.893-1.604-22.048-3.261-57.509-5.556-87.871-7.36-132.059-7.842-23.239-.254-33.617-.116-50.627.674-11.629.54-42.371 2.494-46.696 2.967-2.359.259 8.133-3.625 26.504-9.81 23.239-7.825 27.934-10.149 28.304-14.005.417-4.348-3.529-6-16.878-7.066Z" />
                        </svg>
                        <span className="relative">Love</span>
                    </span>{' '}
                    compatibility
                </h1>

Then below this title, we will have two inputs that accept the couple's name. In react we must use the state to manage these dynamic variables so we'll first declare two state variables called fname and sname with an initial value of an empty string.

    const [fname, setfname] = useState("")
    const [sname, setsname] = useState("")

Now that we have these state variables set, we can create two input fields to accept names as follows

{/* First Input field */

                        <div className="flex flex-col">
                            <label htmlFor="fname" className="text-gray-700  text-xl font-bold leading-tight tracking-normal mb-2">
                                Your Name
                            </label>
                            <input id="fname" className="text-gray-600  focus:outline-none focus:border focus:border-pink-500 font-normal w-64 bg-white  h-10 flex items-center pl-3 text-sm border-gray-300  rounded border shadow" placeholder="Enter your name" />
                        </div>

{/* Second Input Field */
                        <div className="flex flex-col">
                            <label htmlFor="lname" className="text-gray-600  text-xl font-bold leading-tight tracking-normal mb-2">
                                Partner's Name
                            </label>
                            <input id="lname" className="text-gray-700  focus:outline-none focus:border focus:border-pink-500 font-normal w-64 bg-white  h-10 flex items-center pl-3 text-sm border-gray-300  rounded border shadow" placeholder="Enter your beloved one's name" />
                        </div>

If you notice the final build carefully we also have a Pounding heart between our two inputs. This heart icon is from Font Awesome icons. To use this you can install this package from the npm

Once you have installed this package you can import this at the top of our calculator component as

import { FaHeart } from "react-icons/fa";

Between these inputs, we will use our newly imported Heart icon within a <div> that positions it accordingly between our inputs using flex property. We also specify the width and height of the icon using w-10 and h-10 utility classes from tailwind.

<div className='w-full flex flex-col items-center justify-center mx-auto mt-5'>
<FaHeart className="text-[#E90606] custom-css-pound w-10 h-10" />
</div>

We also apply the Pounding effect to our heart icon using custom-css-pound. You can easily create a pounding effect using the following CSS Code

.custom-css-pound {
    animation: animateHeart 1.2s infinite;
}

@keyframes animateHeart {
    0% {
      transform:  scale(0.8);
    }
    5% {
      transform:  scale(0.9);
    }
    10% {
      transform:  scale(0.8);
    }
    15% {
      transform:  scale(1);
    }
    50% {
      transform:  scale(0.8);
    }
    100% {
      transform:  scale(0.8);
    }
  }

Now in order to make our input tag work we must also add a value attribute to our input tag and set its value to our state variable fname. Now that we have bound our state variable with the input we must also use add an onChange event handler that sets our state variable to the value typed by the user using the setter function defined with our state variable as follows

// For first input
<input value={fname} onChange={(e) => setfname(capitalizeFirstLetter(e.target.value))}

//For second input
<input value={lname} onChange={(e) => setlname(capitalizeFirstLetter(e.target.value))}

If you notice carefully instead of directly updating our state on the change we also capitalize the first character input by User. This is a simple functionality I thought of implementing. One can easily achieve this by using a simple utility function as follows


    // Utility function to capitalize first character of Name

    function capitalizeFirstLetter(string) {
        return string.charAt(0).toUpperCase() + string.slice(1);
    }

This function just takes a string as a parameter and returns a string with the first character being capitalized using the .toUppercase() method of strings.

Now that we handled input we can create a button below our input fields that sends a request to our API

  {/* Button */
                <div className='flex  justify-center items-center pt-6'>
                    <button onClick={calculate} type="button" className="text-white flex items-center bg-gradient-to-br from-pink-500 to-orange-400 hover:bg-gradient-to-bl focus:ring-4 focus:outline-none focus:ring-pink-200 font-medium rounded-lg text-sm px-5 py-2.5 text-center mr-2 mb-2">
                        <svg fill='white' className='text-white' width="24" height="24" xmlns="http://www.w3.org/2000/svg" fillRule="evenodd" clipRule="evenodd"><path d="M12 21.593c-5.63-5.539-11-10.297-11-14.402 0-3.791 3.068-5.191 5.281-5.191 1.312 0 4.151.501 5.719 4.457 1.59-3.968 4.464-4.447 5.726-4.447 2.54 0 5.274 1.621 5.274 5.181 0 4.069-5.136 8.625-11 14.402m5.726-20.583c-2.203 0-4.446 1.042-5.726 3.238-1.285-2.206-3.522-3.248-5.719-3.248-3.183 0-6.281 2.187-6.281 6.191 0 4.661 5.571 9.429 12 15.809 6.43-6.38 12-11.148 12-15.809 0-4.011-3.095-6.181-6.274-6.181" /></svg>
                        <span className='ml-2 font-semibold'>Calculate %</span>
                    </button>

                </div>

Above is a simple button of the type button which on click will invoke a function called calculate.

Before implementing calculate() function let's understand what it does. Calculate function makes a request to our API and sets the result.

The API that we using for this project is this API from RapidAPI. This is a popular API and the creator of the API has specified how to use the API. So we will follow the same in our calculate() function.

In order to make API requests we will use a popular package called Axios. In order to use Axios install it using npm install axios. Then import it at the top of the component as follows

// Heart Icon from Font Awesome being imported
import { FaHeart } from "react-icons/fa";

//Importing Axios
import axios from 'axios';

IMPORTANT:- In order to use this API, you need to create an account on Rapid API.

Once you have created an account you will be provided with a RapidAPI-Key which will be essential further while making API calls.

One final step before implementing calculate function is to set up a state variable to store results obtained from API. So create a state variable called data. Since the API returns an Object as the response we will initialize this state variable with an empty object as follows

const [data, setdata] = useState({})

//Along with this, we will also use another piece of state to display data dynamically.

const [isPending, setisPending] = useState(false)

This is used to perform a simple check before displaying results using conditional rendering

Now that we have everything set up let's implement the calculate() function as follows

  const calculate = () => {
        setisPending(true)
        const options = {
            method: 'GET',
            url: 'https://love-calculator.p.rapidapi.com/getPercentage',
            params: { fname: fname, sname: sname },
            headers: {
                'X-RapidAPI-Key': YOUR API KEY,
                'X-RapidAPI-Host': 'love-calculator.p.rapidapi.com'
            }
        };
        try {
            axios.request(options)
                .then(data => {
                    setdata(data);
                    setisPending(false);
                })
        } catch (error) {
            console.log(error)
        }
    }

This functional initially sets isPending to true to indicate that the result is pending. Then we create options to pass into our Axios request as specified by RapidAPI docs.

This option species few important things they are

  1. The type of request i.e, GET

  2. API endpoint i.e, "https://love-calculator.p.rapidapi.com/getPercentage"

  3. Parameters for our request are our fname and sname state variables which contain the names provided by our user through our controlled input fields

  4. Then finally we have headers that contain the RapidAPI key and RapidAPI-Host

Then we finally make a request to our API using Axios by providing these options. Here we use promises and resolve the promise using .then block within which we update our state variable data with the actual data returned by our API and we also set isPending to false as the result is obtained. We wrap our entire request part within a try-catch block for error handling if in case something fails.


Now that we have implemented all the necessary functionality of our, the last thing and the important thing remaining is displaying the results to our Users. For this, we will be using a technique called Conditional Rendering.

Conditional rendering in React refers to the technique of displaying different content or components based on certain conditions. This can be done using if-else statements, ternary operators, or the && operator. If you want to know more about conditional rendering you can refer to the new react docs here

So In our application, we use the ternary operator to display different UI upon different conditions. First, we will check whether isPending is true, if it's true it means we are still calculating results. So till the result is calculated we display a loader. Else if isPending is false we further check whether our data is available or not using Object.keys(data).length > 0. This piece of code ensures that data is available using the length property on object keys.

Once the data is available then we create a Horizontal Card which displays two names and their Love percentage obtained from the API. Our API result consists of an Object within which it contains a data property that further contains fname and sname (the parameters we have passed to the API) along with a percentage and a result, which contains some text based on the percentage of Love. So we can access these using the dot operator available on Objects and Display them within our card using curly braces (for ex:- {data.data.fname} )

{/* Display result conditionally*/}
                {
                    isPending ? (
                        <div className="flex justify-center items-center">
                            <div
                                className="m-12 inline-block h-8 w-8 animate-spin rounded-full border-4 border-pink-500  border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"
                                role="status">
                                <span
                                    className="!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]"
                                >Loading...</span
                                >
                            </div>
                        </div>
                    ) : (
                        Object.keys(data).length > 0 && (
                            <div
                                className="
              w-full
              mx-auto
              rounded-lg
              bg-pink-50
              shadow-lg
              px-5
              pt-2
              pb-4
              text-gray-800
              mt-2
            "
                                style={{ maxWidth: '500px' }}
                            >
                                <div className="w-full pb-5 "></div>
                                <div className="w-full">
                                    <p className="text-md text-gray-500 font-thin text-center">Percentage of Love Between</p>
                                    <p className="text-2xl text-pink-500 font-bold text-center">
                                        {data.data.fname} <span>&</span> {data.data.sname} is
                                    </p>
                                </div>

                                {/* Progress Bar */}
                                <div className='flex justify-center items-center'>
                                    <Progress percentage={data.data.percentage} />
                                </div>

                                <div className="w-full mb-4">
                                    <div className="text-3xl text-pink-500 text-left leading-tight h-3"></div>
                                    <p className="text-xl text-gray-600 text-center px-5">
                                        {data.data.result}
                                    </p>
                                    <div
                                        className="text-3xl text-pink-500 text-right leading-tight h-3 -mt-3"
                                    ></div>
                                </div>
                            </div>

                        ))
                }

If you see the live demo of our App over here, You can also notice that we have a dynamic progress bar that displays progress or in our case percentage of love. For this progress bar, we can create a custom component called Progress in our src/components

import React ,{ useState } from "react";
const Progress = ({percentage}) => {
    const [style, setStyle] = React.useState({});

    setTimeout(() => {
        const newStyle = {
            opacity: 1,
            width: `${percentage}%`
        }

        setStyle(newStyle);
    }, 300);

    return (
        <div className="progress">
            <div className="progress-done" style={style}>
                {percentage}%
            </div>
        </div>
    )
}

export default Progress

This progress component takes a percentage as a prop and displays a beautiful progress bar with a transition from 0% to the percentage of love obtained from our API.

This progress component is from Florin Pop. You can check the codepen here.

This progress bar also requires custom styling. The styles used are as follows

.progress {
    @apply bg-[#d8d8d8] relative h-[30px] w-[300px] mx-0 my-[15px] rounded-[20px];
  }
  .progress-done {
    @apply shadow-[0_3px_3px_-5px_#F2709C,0_2px_5px_#F2709C] text-white flex items-center justify-center h-full w-0 opacity-0 transition-[1s] duration-[ease] delay-[0.7s] rounded-[20px];
    background: linear-gradient(to left, #f2709c, #ff9472);
  }

we use the @apply directive of tailwind to achieve this. You can also modify the styling accordingly and you can also play around with transition properties like duration, opacity, and so on to obtain desired results.

Now within our Calculator the component we import and use this custom Progress component and pass the percentage obtained from our API response as a prop and it returns a JSX representing the progress bar.

<div className='flex justify-center items-center'>
     <Progress percentage={data.data.percentage} />
</div>

That is it. We have completely built a responsive and fun love calculator app. The source code is available over here and the live version of the app is here.

You can add additional features to this app and can deploy using Vercel, Netlify, Cloudflare, etc...

0
Subscribe to my newsletter

Read articles from Subramanya M Rao directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Subramanya M Rao
Subramanya M Rao

CS Undergrad pursuing Web Development. Keen Learner & Tech Enthusiast.