Building a Simple Counter App: From Classical HTML to React with Components

Building a Simple Counter App: From Classical HTML to React with Components

This tutorial guides you through creating a basic counter app using the classical approach (HTML, CSS, JavaScript) and then rebuilding it with React, a powerful JavaScript library for user interfaces. We start with the classical method to understand traditional web development, then transition to React, introducing components and state management. A dedicated section teaches how to create and insert a separate CounterDisplay component, showcasing React’s modularity. The tutorial is designed for beginners and formatted for Google Blogger.

What You’ll Learn

  • How to build a counter app with HTML, CSS, and JavaScript.
  • How elements are inserted into the DOM in the classical approach.
  • Limitations of the classical approach.
  • How to rebuild the app with React, using components and state.
  • How React inserts elements and the role of public/index.html.
  • How to create and insert a separate React component (CounterDisplay).
  • Commands and setup for both approaches.

Prerequisites

  • Basic knowledge of HTML, CSS, and JavaScript.
  • A code editor (e.g., Visual Studio Code, download from code.visualstudio.com).
  • Node.js and npm (for React, download LTS from nodejs.org).

Step 1: Classical Approach - Counter App

Let’s build a counter app using plain HTML, CSS, and JavaScript. This app displays a count and buttons to increment or decrement it.

Setup

Create a folder named classical-counter:

mkdir classical-counter
cd classical-counter

Create three files: index.html, styles.css, and script.js.

Create index.html

This defines the app’s structure.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Classical Counter App</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div id="app">
    <h1>Classical Counter</h1>
    <p>Count: <span id="count">0</span></p>
    <button onclick="increment()">Increment</button>
    <button onclick="decrement()">Decrement</button>
  </div>
  <script src="script.js"></script>
</body>
</html>

Create styles.css

This styles the app.

#app {
  text-align: center;
  margin-top: 50px;
  font-family: Arial, sans-serif;
}

button {
  margin: 5px;
  padding: 10px 15px;
  font-size: 16px;
  cursor: pointer;
  border: none;
  background-color: #4CAF50;
  color: white;
  border-radius: 4px;
}

button:hover {
  background-color: #45a049;
}

Create script.js

This handles the counter logic.

let count = 0;
const countElement = document.getElementById('count');

function increment() {
  count++;
  countElement.textContent = count;
}

function decrement() {
  count--;
  countElement.textContent = count;
}

How Elements Are Inserted

  • HTML Structure: The index.html file defines a static structure with a <div id="app"> containing an <h1>, <p>, <span id="count">, and two <button> elements.
  • DOM Manipulation: In script.js, document.getElementById('count') selects the <span> to update its textContent when count changes.
  • Event Handling: The <button> elements use onclick attributes to call increment() or decrement() functions.
  • State: The count variable is plain JavaScript, and you manually update the DOM to reflect changes.

Run the App

Open index.html in a browser (drag to Chrome or Firefox). Alternatively, use a local server:

npx serve .

Visit http://localhost:3000. You’ll see a heading, the count (starting at 0), and buttons to increment or decrement it.

Limitations of the Classical Approach

While this works for a simple app, it has drawbacks:

  • Manual DOM Updates: You must manually update the DOM (e.g., countElement.textContent), which is error-prone and slow for complex UIs.
  • Mixed Concerns: HTML (structure), CSS (styles), and JavaScript (logic) are separate, making it hard to organize reusable UI pieces.
  • Scalability: Adding features (e.g., a reset button or multiple counters) requires duplicating HTML and JavaScript, increasing complexity.
  • No Optimization: Every state change updates the DOM directly, which can be inefficient.
  • No Modularity: The UI and logic are tightly coupled, making it difficult to reuse or maintain code.

Step 2: Why Switch to React?

React addresses these limitations by offering:

  • Component-Based Structure: UI is built with reusable components (e.g., CounterDisplay), improving organization and reusability.
  • Virtual DOM: React tracks changes in a virtual DOM and updates only the necessary parts of the real DOM, boosting performance.
  • State Management: React’s useState Hook simplifies state updates and automatically syncs the UI.
  • Modularity: Components can be created and reused across the app, unlike the classical approach’s flat structure.
  • Developer Experience: Tools like Create React App provide hot reloading and a modern development setup.
  • Scalability: React makes it easy to add features, manage complex state, and maintain large apps.

Switching to React transforms the counter app into a modular, efficient, and maintainable project. Components, as we’ll see with CounterDisplay, make it easy to extend and organize the app.

Step 3: React Approach - Counter App

Let’s rebuild the counter app using React.

Setup

Install Node.js and npm (if not done). Verify:

node -v
npm -v

Example output: v20.17.0 (Node), 10.8.3 (npm).

Create a React project:

npx create-react-app my-first-react-app
cd my-first-react-app

Start the development server:

npm start

Opens http://localhost:3000 with auto-reload.

Project Structure

The React project, after adding the CounterDisplay component, has:

my-first-react-app/
├── public/
│   ├── index.html    # Single HTML page
│   ├── favicon.ico
│   └── manifest.json
├── src/
│   ├── App.js        # Main component
│   ├── App.css       # Component styles
│   ├── CounterDisplay.js  # New component
│   ├── index.js      # Renders app to DOM
│   └── index.css     # Global styles
├── package.json      # Dependencies and scripts
└── README.md

Key Files:

  • public/index.html: The single HTML page where React mounts the app.
  • src/index.js: Mounts the app to the DOM.
  • src/App.js: Defines the main UI component.
  • src/CounterDisplay.js: A separate component for displaying the count.

Content of public/index.html

This is the default index.html created by Create React App:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

Role: This file is the browser’s entry point. The <div id="root"> is where React inserts all components. The %PUBLIC_URL% placeholders are replaced during the build process to reference assets in the public folder.

Modify src/App.js

Replace src/App.js with the counter app, initially without the separate component:


import React, { useState } from 'react';
import './App.css';

function App() {
  const [count, setCount] = useState(0);

  return (
    <div className="App">
      <h1>React Counter</h1>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
    </div>
  );
}

export default App;

Update src/App.css

Replace src/App.css for styling:

.App {
  text-align: center;
  margin-top: 50px;
  font-family: Arial, sans-serif;
}

button {
  margin: 5px;
  padding: 10px 15px;
  font-size: 16px;
  cursor: pointer;
  border: none;
  background-color: #4CAF50;
  color: white;
  border-radius: 4px;
}

button:hover {
  background-color: #45a049;
}

How Elements Are Inserted in React

  • JSX: The App component returns JSX (e.g., <h1>, <p>, <button>), a JavaScript syntax resembling HTML.
  • Rendering to DOM: In src/index.js (default content):
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
  • ReactDOM.createRoot targets <div id="root"> in public/index.html.
  • <App /> is rendered, inserting JSX elements into the DOM.
  • Virtual DOM: React maintains a virtual DOM. When count changes (via setCount), React compares the virtual and real DOM, updating only changed elements (e.g., the <p> showing count).
  • State Management: useState manages count. Updates trigger re-renders, but React optimizes DOM changes.

Run the App

Save files. The browser (http://localhost:3000) shows the counter app. It looks and works like the classical version but is built with components.

Step 4: Adding a Separate Component

Let’s enhance the React app by creating a CounterDisplay component to display the count, demonstrating React’s modularity.

Create src/CounterDisplay.js

Create a new file CounterDisplay.js in the src folder:

import React from 'react';

function CounterDisplay({ count }) {
  return (
    <p className="counter-display">Current Count: {count}</p>
  );
}

export default CounterDisplay;

Explanation:

  • { count }: Receives count as a prop (like a function parameter).
  • className="counter-display": Applies a CSS class for styling.
  • export default: Makes the component importable.

Update src/App.js

Modify src/App.js to use CounterDisplay:

import React, { useState } from 'react';
import CounterDisplay from './CounterDisplay';
import './App.css';

function App() {
  const [count, setCount] = useState(0);

  return (
    <div className="App">
      <h1>React Counter</h1>
      <CounterDisplay count={count} />
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
    </div>
  );
}

export default App;

Changes:

  • import CounterDisplay: Imports the new component.
  • <CounterDisplay count={count} />: Inserts the component, passing count as a prop.
  • Removed the old <p>Count: {count}</p>.

Update src/App.css

Add a style for the new component in src/App.css:

.App {
  text-align: center;
  margin-top: 50px;
  font-family: Arial, sans-serif;
}

button {
  margin: 5px;
  padding: 10px 15px;
  font-size: 16px;
  cursor: pointer;
  border: none;
  background-color: #4CAF50;
  color: white;
  border-radius: 4px;
}

button:hover {
  background-color: #45a049;
}

.counter-display {
  font-size: 18px;
  font-weight: bold;
  color: #333;
  margin: 10px 0;
}

How the Component Is Inserted

  • Component Creation: CounterDisplay is a functional component defined in its own file, making it reusable.
  • Props: The count prop is passed to CounterDisplay via count={count}, allowing the component to display the current count.
  • Insertion: In App.js, <CounterDisplay count={count} /> is included in the JSX, and React renders it as part of the App component’s output, inserting it into the DOM via the virtual DOM.
  • Reusability: You could insert CounterDisplay multiple times with different props or in other components.

Run the App

Save all files. The browser (http://localhost:3000) updates to show the counter app with the count displayed by CounterDisplay. The functionality remains the same, but the code is now more modular.

Why Components Matter

Creating a separate CounterDisplay component demonstrates React’s power:

  • Separation of Concerns: The display logic is isolated, making App.js cleaner.
  • Reusability: CounterDisplay can be used elsewhere in the app or in other projects.
  • Maintainability: Changes to the display (e.g., styling or formatting) are made in one place.
  • Scalability: Adding more components (e.g., a reset button component) is straightforward.

Step 5: Comparing Classical vs. React

Aspect Classical (HTML/CSS/JS) React
Element Insertion Static HTML; JavaScript manipulates DOM (e.g., textContent). JSX in JavaScript, rendered to <div id="root"> via ReactDOM.
DOM Updates Manual updates, potentially slow and error-prone. Virtual DOM updates only changed elements, fast and efficient.
State Management Plain variables; manual DOM sync. useState Hook auto-syncs state and UI.
Structure Flat HTML; mixed concerns, hard to reuse. Component-based; modular and reusable (e.g., App, CounterDisplay).
Modularity No clear way to create reusable UI pieces. Components (e.g., CounterDisplay) encapsulate UI and logic, reusable across the app.
Scalability Complex to add features; repetitive code. Easy to add components, props, and state for complex apps.
Development Basic setup, no auto-reload. Hot reloading, build tools (Create React App), modern workflow.

Motivation for React

  • Modularity: Components like App and CounterDisplay encapsulate UI and logic, unlike the classical approach’s scattered HTML and JavaScript.
  • Efficiency: The virtual DOM minimizes DOM operations, unlike manual updates in JavaScript.
  • Reusability: Components can be reused (e.g., CounterDisplay for multiple displays), while classical apps require duplicating code.
  • Maintainability: React organizes code into components, making large projects easier to manage than mixed HTML/CSS/JS files.
  • Scalability: Adding features (e.g., new components) is straightforward in React, unlike the classical approach’s growing complexity.
  • Ecosystem: React’s tools (e.g., hot reloading) and community support streamline development compared to vanilla JavaScript’s basic setup.

Step 6: Build and Deploy (React Only)

Build the React app:

npm run build

Creates a build/ folder with optimized files.

Test locally:

npm install -g serve
serve -s build

Visit http://localhost:3000.

Deploy (optional): Upload the build/ folder to Netlify for a live URL.

Step 7: Clean Up

Classical App:

rm -rf classical-counter

React App:

Stop the server (Ctrl + C). Delete the folder:

rm -rf my-first-react-app

Step 8: Key React Concepts

  • Components: Reusable UI pieces (e.g., App, CounterDisplay).
  • JSX: HTML-like syntax in JavaScript, compiled to DOM elements.
  • State: useState manages dynamic data, triggering efficient re-renders.
  • Props: Pass data to components (e.g., count to CounterDisplay) for flexibility.
  • Virtual DOM: Optimizes updates by comparing changes.

Next Steps

Classical:

  • Add a reset button to see growing complexity.
  • Explore vanilla JavaScript frameworks for better structure.

React:

  • Learn Hooks (useEffect, useContext).
  • Create more components (e.g., a ResetButton component).
  • Use Tailwind CSS for styling.
  • Explore React Router for multi-page apps.

Resources:

This tutorial started with a classical HTML, CSS, and JavaScript counter app to show traditional DOM manipulation, then transitioned to React to demonstrate its modular, efficient approach. Adding a CounterDisplay component highlighted React’s component-based structure, making the app more maintainable and scalable.

Comments

Popular posts from this blog

Building and Deploying a Recommender System on Kubeflow with KServe

CrewAI vs LangGraph: A Simple Guide to Multi-Agent Frameworks

Tutorial: Building Login and Sign-Up Pages with React, FastAPI, and XAMPP (MySQL)