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 itstextContent
whencount
changes. - Event Handling: The
<button>
elements useonclick
attributes to callincrement()
ordecrement()
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">
inpublic/index.html
.<App />
is rendered, inserting JSX elements into the DOM.- Virtual DOM: React maintains a virtual DOM. When
count
changes (viasetCount
), React compares the virtual and real DOM, updating only changed elements (e.g., the<p>
showingcount
). - State Management:
useState
managescount
. 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 }
: Receivescount
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, passingcount
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 toCounterDisplay
viacount={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 theApp
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
andCounterDisplay
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
toCounterDisplay
) 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:
- React Docs
- MDN Web Docs for JavaScript
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
Post a Comment