Welcome to this in-depth guide to learning React. This tutorial is designed to take you from the absolute basics to more advanced concepts, providing detailed explanations and code examples along the way.
1. React Intro
What is React?
React is a free and open-source front-end JavaScript library for building user interfaces or UI components. It is maintained by Meta (formerly Facebook) and a community of individual developers and companies. React can be used as a base in the development of single-page, mobile, or server-rendered applications with frameworks like Next.js.
The core idea of React is simple: to allow developers to build large applications with data that changes over time, without needing to reload the page. Its main purpose is to be fast, scalable, and simple. It focuses only on the user interface, or the “View” layer in the Model-View-Controller (MVC) pattern. This means it can be easily integrated with other JavaScript libraries or frameworks.
Why Use React?
There are several reasons why React has become so popular among developers:
- Component-Based Architecture: React is built around the concept of reusable components. You can break down your UI into small, isolated pieces of code (components). For example, a chat application could have components for the user list, the chat window, and the message input field. These components can be reused and nested, making your code more modular, manageable, and easier to debug.
- Declarative UI: React uses a declarative approach to UI development. You simply “declare” what you want the UI to look like for a given state of your application, and React takes care of updating the actual DOM to match that state. You don’t have to manually manipulate the DOM (e.g., “find this element and change its text”). This makes the code more predictable and easier to understand.
- Imperative (what you avoid in React):
// Find the element with id 'msg'
let p = document.getElementById('msg');
// Change its content and color
p.textContent = 'Hello, World!';
p.className = 'greeting';
- Declarative (the React way):
function Greeting() {
return <p className="greeting">Hello, World!</p>;
}
- The Virtual DOM: One of React’s most significant performance features is the Virtual DOM. Instead of directly manipulating the browser’s DOM, which can be slow and resource-intensive, React maintains a lightweight copy of the DOM in memory—the Virtual DOM.
When the state of a component changes, React first updates the Virtual DOM. Then, it compares the updated Virtual DOM with a snapshot of the Virtual DOM taken right before the update. This process is called “diffing.” React identifies exactly what has changed (the “diffs”) and then updates only those specific parts of the real browser DOM. This minimal update process is much faster than re-rendering the entire UI, leading to significant performance gains. - Unidirectional Data Flow: In React, data flows in a single direction, from parent components down to child components via “props”. This makes it easier to trace where data comes from and how it affects the application, simplifying debugging and making the application’s logic more predictable. If a child component needs to change data, it sends a message up to its parent component (usually via a callback function) to request the change. The parent then updates its state, and the new data flows back down.
- Strong Community and Ecosystem: React has a massive, active community. This means there are countless tutorials, articles, forums, and third-party libraries available. This rich ecosystem includes state management libraries (like Redux and Zustand), routing libraries (like React Router), UI component libraries (like Material-UI and Ant Design), and much more.
React’s Core Philosophy
The goal of React is to enable developers to reason about their applications easily. By breaking the UI into components and managing state changes declaratively, React helps manage the complexity that is inherent in modern web applications. It provides a solid foundation, allowing you to focus on building your features rather than fighting with the intricacies of DOM manipulation.
2. React Get Started
Setting Up a React Environment
The fastest and most recommended way to start a new single-page React application is by using Create React App. It’s an officially supported toolchain that sets up a complete development environment for you with just one command. You don’t need to configure build tools like Webpack or Babel; they are pre-configured and hidden so you can focus on writing code.
Prerequisites: You need to have Node.js and its package manager, npm, installed on your computer. You can download them from nodejs.org.
Creating a New App: Open your terminal or command prompt and run the following command:
npx create-react-app my-react-app
- npx is a package runner tool that comes with npm 5.2+. It ensures you are always using the latest version of Create React App.
- my-react-app is the name of the folder that will be created for your project.
This command will create a new directory called my-react-app, download all the necessary packages, and set up a basic project structure for you. This might take a few minutes.
Understanding the Folder Structure
Once the setup is complete, navigate into your new project directory:
cd my-react-app
Let’s look at the most important files and folders that were created:
my-react-app/
├── node_modules/ # Contains all the third-party libraries and dependencies.
├── public/ # Contains public assets.
│ ├── favicon.ico
│ ├── index.html # The main HTML page for your app.
│ └── ...
├── src/ # The heart of your application. All your React code goes here.
│ ├── App.css
│ ├── App.js # The main application component.
│ ├── App.test.js
│ ├── index.css
│ ├── index.js # The entry point for your React application.
│ └── ...
├── .gitignore
├── package.json # Lists project dependencies and scripts.
└── README.md
- public/index.html: This is the only HTML file in your project. React will inject your entire application into this page. You will typically not edit this file much, except maybe to change the <title>. The most important part of this file is <div id=”root”></div>. This div is the “root” DOM node where your entire React application will be mounted.
- src/index.js: This is the JavaScript entry point. It’s responsible for finding the root element from index.html and telling React to render our main application component (<App />) inside it.
- src/App.js: This is the root component of your application. When you start a new project, it contains the default content you see on the screen. This is where you’ll start building your application.
- package.json: This file is crucial for any Node.js project. It contains metadata about your project, a list of its dependencies (react, react-dom, etc.), and a set of scripts you can run.
Running the Development Server
Create React App comes with a live development server. To start it, run the following command from your project directory:
npm start
This will:
- Start the development server (usually on http://localhost:3000).
- Open your default web browser to that address.
- Watch all your files for changes. Whenever you save a file, the browser will automatically reload to show the latest changes. This feature is called “Hot-Reloading” and is incredibly useful for development.
You should now see the default React welcome page in your browser. You can start editing the code in src/App.js, and the changes will appear instantly.
Building for Production
When you are ready to deploy your application, you need to create an optimized production build. This process will bundle your code, minify it to make it smaller, and optimize it for performance.
Run this command:
npm run build
React Upgrade
Why Upgrade?
React is actively maintained, and new versions are released regularly. These updates often include:
- New Features: Such as Hooks, which were introduced in React 16.8.
- Performance Improvements: Optimizations to the diffing algorithm and rendering process.
- Bug Fixes and Security Patches: Addressing issues found in previous versions.
Keeping your React version up-to-date is essential for security, performance, and access to the latest features that can make development easier and more powerful.
How to Upgrade React
If your project was created with Create React App, upgrading is usually straightforward. Create React App bundles react, react-dom, and other related packages into a single dependency called react-scripts. Upgrading react-scripts will typically upgrade React itself along with all the underlying build configurations.
Step 1: Check Your Current Versions Before upgrading, it’s good to know your current versions. You can find them in your package.json file. Look for react, react-dom, and react-scripts.
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
// ...
}
Step 2: Read the Release Notes This is a critical step. Before any major upgrade (e.g., from version 17 to 18), you should always read the official React blog and the changelog. They will detail any “breaking changes”—changes that might require you to modify your existing code to make it compatible with the new version.
For example, the upgrade to React 18 introduced a new concurrent rendering model and a new root API (createRoot instead of ReactDOM.render), which required specific changes in the src/index.js file.
Step 3: Upgrading react-scripts To upgrade to the latest version of react-scripts, you can run:
npm install react-scripts@latest
This will fetch the newest stable version and update your package.json and package-lock.json files.
Step 4: Upgrading react and react-dom Often, updating react-scripts is enough. However, you might want to update react and react-dom to the latest versions directly.
npm install react@latest react-dom@latest
After running the install commands, your package.json should reflect the new versions.
Step 5: Install Dependencies and Test After updating the packages, run npm install to make sure all dependencies are correctly installed based on the updated package-lock.json.
npm install
Finally, run your application’s test suite and start the development server to manually check if everything is working as expected.
npm test
npm start
Look for any warnings or errors in the console. The React team often provides helpful warnings about deprecated features and guides on how to update your code.
A Note on Manual Setups
If your project does not use Create React App and has a manual Webpack/Babel setup, the upgrade process is more involved. You will need to upgrade react and react-dom as shown above, but you may also need to update other related dependencies, such as:
- Babel presets and plugins (@babel/preset-react).
- Webpack loaders and plugins.
- Testing libraries that integrate with React.
In these cases, carefully following the official migration guides is absolutely essential.
This will create a build folder in your project directory. This folder contains all the static files (HTML, CSS, and JavaScript) that you can deploy to any web hosting service.
React ES6
React heavily leverages features from ES6 (ECMAScript 2015) and later versions of JavaScript. A good understanding of modern JavaScript syntax is crucial for writing clean, efficient, and modern React code. While React can be written in older JavaScript (ES5), using ES6 makes it much more pleasant and concise.
Here are the most important ES6 features you will use constantly in React development.
let and const
Before ES6, var was the only way to declare variables. var is function-scoped and can be re-declared, which can lead to bugs. ES6 introduced let and const, which are block-scoped ({…}).const: Used for declaring constants. The value cannot be reassigned. This is the preferred way to declare variables that won’t change, which includes most variables in a typical React component.
const name = 'Alice';
// name = 'Bob'; //
This will throw an error
let: Used for variables that you expect to reassign later.
let age = 30;
age = 31; // This is fine
In React: You should default to using const and only use let when you know a variable needs to be reassigned (e.g., in a loop counter).
Arrow Functions =>
Arrow functions provide a more concise syntax for writing functions and lexically bind the this value, which solves a common problem in class-based components.
Standard Function:
function add(a, b) {
return a + b;
}
Arrow Function:
const add = (a, b) => {
return a + b;
};
// For single expression functions, you can omit the {} and return
const subtract = (a, b) => a - b;
In React: Arrow functions are widely used for component definitions (functional components), event handlers, and operations inside methods like .map() and .filter().
Modules (import / export)
ES6 introduced a standardized module system for JavaScript, allowing you to split your code across multiple files.
- export: Used to export functions, objects, or primitives from a file (a module) so they can be used in other files.
Named Export:
// utils.js
export const pi = 3.14;
export const greet = (name) => `Hello, ${name}`;
Default Export: Each file can have only one default export.
// MyComponent.js
const MyComponent = () => <div>Hello</div>;
export default MyComponent;
import: Used to import the exported code into another file.
// App.js
import MyComponent from ‘./MyComponent.js’; // Default import
import { pi, greet } from ‘./utils.js’; // Named imports
In React: Modules are the backbone of structuring a React application. Every component, utility function, and style sheet is typically its own module.
Destructuring
Destructuring is a convenient way to extract values from arrays or properties from objects into distinct variables.
Object Destructuring:
const person = { firstName: 'John', lastName: 'Doe' };
const { firstName, lastName } = person; // firstName = 'John', lastName = 'Doe'
Array Destructuring:
const colors = ['red', 'green', 'blue'];
const [firstColor, secondColor] = colors; // firstColor = 'red', secondColor = 'green'
In React: Destructuring is heavily used to access props and state. It makes the code much cleaner.
// Instead of this:
function Greeting(props) {
return <h1>Hello, {props.name}</h1>;
}
// You can write this:
function Greeting({ name }) { // Destructuring props directly
return <h1>Hello, {name}</h1>;
}
Spread and Rest Operators (…)
The … syntax can be used for both “spread” and “rest”.Spread Operator: “Spreads” the elements of an array or the properties of an object into another array or object. It’s very useful for creating copies or merging.
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
In React: The spread operator is essential for updating state immutably. You create a copy of the state, modify the copy, and then set the state to the new copy.
Rest Operator: Collects the “rest” of the elements or properties into a new array or object. It’s often used in function parameters.
function logArgs(first, ...rest) {
console.log(first); // 'a'
console.log(rest); // ['b', 'c']
}
logArgs('a', 'b', 'c');
ES6 Classes
Although modern React favors functional components with Hooks, understanding ES6 classes is important for working with older codebases or certain advanced cases. Classes are a blueprint for creating objects.
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello, I'm ${this.name}`);
}
}
In React: Classes are used to define “Class Components,” which have state and lifecycle methods. (More on this in the “React Class” section).
5. React Render HTML
At its core, a React application’s purpose is to render some UI into the browser’s DOM. React provides a clean and efficient way to do this.
The Entry Point: ReactDOM.render()
React separates the logic of the library itself (react) from the part that interacts with the browser (react-dom). The react-dom library provides the specific method for rendering your React components into the DOM.
The primary method used for this is ReactDOM.render(). (Note: In React 18, this has been superseded by createRoot().render(), but the concept is the same).
Let’s break down how it works, using the pre-React 18 syntax for historical context and the new syntax for modern applications.
The public/index.html File As mentioned in the “Get Started” section, your project has a simple HTML file. This file is the shell for your application. Inside it, there’s a single <div>:
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... -->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
React will be injected into the div above.
-->
</body>
</html>
This <div id=”root”></div> is our target. It’s the container where our entire React application will live.
The src/index.js File This JavaScript file is the bridge between our React components and the index.html file. Its job is to:
- Import the necessary libraries (React, ReactDOM).
- Import our main application component (usually <App />).
- Find the root DOM element.
- Tell React to render our component tree inside that element.
The Modern Way (React 18 and newer)
In React 18, a new Root API was introduced to enable concurrent features. This is the standard way to initialize an app now.
src/index.js (React 18+)
import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
// 1. Get the container element from the DOM.
const container = document.getElementById('root');
// 2. Create a "root" for React to manage.
const root = createRoot(container);
// 3. Render the main App component into the root.
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Breakdown:
- createRoot(container): This new function tells React that we want to create a new “root” renderer that will control the contents of the container DOM node (<div id=”root”></div>).
- root.render(<App />): We then call the render method on this root to display our React element (<App />) inside it.
- <React.StrictMode>: This is a wrapper component that React provides to help you find potential problems in your application. It activates additional checks and warnings for its descendants. It only runs in development mode and does not affect the production build.
The Legacy Way (React 17 and older)
It’s useful to know the older API as you’ll encounter it in many existing tutorials and projects.
src/index.js (React 17)
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Breakdown: The ReactDOM.render() function takes two arguments:
- What to render: A React element. Here, it’s our entire <App /> component, wrapped in <React.StrictMode>.
- Where to render it: A regular DOM element. We use document.getElementById(‘root’) to get a reference to our container div.
What is a “React Element”?
The first argument to render() is a React element. It’s important to understand that what looks like HTML (the <h1> or <App /> syntax) is actually JSX. JSX is a syntax extension for JavaScript.
When React sees <App />, it’s not an HTML string. It’s a JavaScript object that describes what should be rendered. For example, <h1>Hello</h1> gets compiled by Babel into something like this:
React.createElement("h1", null, "Hello");
This function call creates an object that describes the h1 tag. ReactDOM.render() then takes this object and efficiently translates it into actual DOM nodes that the browser can display.
This abstraction is powerful because it allows React to do things like the Virtual DOM diffing before touching the real, slow DOM. It’s also what allows React to be used in non-browser environments (like mobile apps with React Native), because React.createElement() is platform-agnostic. The react-dom library is the specific “renderer” for the web.
6. React JSX
JSX stands for JavaScript XML. It is a syntax extension for JavaScript that was written to be used with React. JSX looks very much like HTML, but it is not HTML. It is a way to write what looks like markup directly within your JavaScript code, making component rendering logic more intuitive and readable.
Browsers don’t understand JSX. It needs to be compiled into regular JavaScript before it can be executed. When you use Create React App or a similar toolchain, a compiler like Babel works behind the scenes to transform your JSX into React.createElement() function calls.
Example:
This JSX code:
const element = <h1 className="greeting">Hello, world!</h1>;
Is compiled by Babel into this JavaScript object:
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
Key Features of JSX
1. Expressions in Curly Braces {}
You can embed any valid JavaScript expression inside JSX by wrapping it in curly braces. This is one of the most powerful features of JSX.
const name = 'Alice';
const element = <h1>Hello, {name}</h1>; // Renders "Hello, Alice"
function formatUser(user) {
return user.firstName + ' ' + user.lastName;
}
const user = { firstName: 'Bob', lastName: 'Smith' };
const element2 = <h1>Hello, {formatUser(user)}</h1>; // Renders "Hello, Bob Smith"
You can put anything inside the braces: variables, function calls, arithmetic operations, etc.
2. JSX Attributes
JSX uses a camelCase naming convention for HTML attributes, since JSX is closer to JavaScript than HTML.
- class becomes className (because class is a reserved keyword in JavaScript).
- tabindex becomes tabIndex.
- for becomes htmlFor.
// HTML
// <label for="email" class="form-label">Email</label>
// JSX
const myLabel = <label htmlFor="email" className="form-label">Email</label>;
You can also pass string literals as attributes (with quotes) or JavaScript expressions (with curly braces).
const imageUrl = 'https://example.com/image.png';
const element = <img src={imageUrl} alt="An example image" />;
3. JSX is an Expression
After compilation, JSX expressions become regular JavaScript objects. This means you can use JSX inside of if statements and for loops, assign it to variables, accept it as arguments, and return it from functions.
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatUser(user)}!</h1>; // Return JSX from a function
}
return <h1>Hello, Stranger.</h1>; // Return different JSX
}
4. Nesting and Child Elements
JSX elements can be nested, just like in HTML.
const element = (
<div>
<h1>Welcome!</h1>
<p>This is a paragraph.</p>
</div>
);
Important: A JSX expression must have exactly one outermost element. If you want to return multiple elements, you must wrap them in a single parent container, like a <div>.
5. Fragments
Sometimes, you don’t want to add an extra <div> to the DOM just to wrap your elements. This can break CSS styling (e.g., with Flexbox or Grid) or create invalid HTML (e.g., a <td> must be inside a <tr>).
React provides Fragments for this purpose. They let you group a list of children without adding extra nodes to the DOM.
Long Syntax:
import React from 'react';
function Columns() {
return (
<React.Fragment>
<td>Hello</td>
<td>World</td>
</React.Fragment>
);
}
Short Syntax (<>…</>):
function Columns() {
return (
<>
<td>Hello</td>
<td>World</td>
</>
);
}
When this is rendered, only the two <td> elements will be added to the DOM, not a wrapper element.
6. Self-Closing Tags
If a tag is empty, you must close it with />, just like in XML.
const element = <img src={imageUrl} />; // Correct
// const element = <img src={imageUrl}>; // Incorrect, will cause an error
7. Booleans, Null, and Undefined are Not Rendered
false, null, undefined, and true are valid children. They simply don’t render. This is useful for conditional rendering.
function MyComponent({ showMessage }) {
return (
<div>
<h1>My App</h1>
{showMessage && <p>This is the secret message!</p>}
</div>
);
}
If showMessage is true, the <p> tag will be rendered. If it’s false, nothing will be rendered in its place.
7. React Components
Components are the heart and soul of React. They are independent and reusable bits of code that serve the same purpose as JavaScript functions, but work in isolation and return HTML (via JSX) to be rendered on the screen. A React application is essentially a tree of components.
There are two main types of components in React:
- Functional Components
- Class Components
Since the introduction of Hooks, Functional Components are the modern standard and are recommended for all new development.
Functional Components
A functional component is a plain JavaScript function that accepts a single argument—an object of properties called props—and returns a React element.
// A simple functional component
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// Using ES6 arrow function syntax (more common)
const Welcome = (props) => {
return <h1>Hello, {props.name}</h1>;
};
// With destructuring props (even cleaner)
const Welcome = ({ name }) => {
return <h1>Hello, {name}</h1>;
};
These components are “stateless” by default (they don’t have their own internal state), but they can manage state and lifecycle events using Hooks (like useState and useEffect).
Class Components
Class components are ES6 classes that extend React.Component. They have a required render() method that returns a React element. They can hold and manage their own state and have access to lifecycle methods.
import React from ‘react’;
class Welcome extends React.Component {
render() {
// Props are accessed via `this.props`
return <h1>Hello, {this.props.name}</h1>;
}
}
While still fully supported, they are considered legacy compared to functional components with Hooks.
Composing Components
Components can refer to other components in their output. This lets us use the same component abstraction for any level of detail. A button, a form, a dialog, a screen: in React, all of these are commonly expressed as components.
// Component 1: A simple Welcome message
const Welcome = ({ name }) => {
return <h1>Hello, {name}</h1>;
};
// Component 2: The main App component that uses Welcome
const App = () => {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
};
// Render the App component
// ReactDOM.render(<App />, document.getElementById('root'));
In this example, the App component is the parent. It renders three instances of the Welcome component, which are its children. This ability to compose components is what makes React so powerful for building complex UIs.
Key Rules of Components
- Component Names Must Start with a Capital Letter: React treats components starting with lowercase letters as DOM tags. For example, represents an HTML div tag, but
represents a component. This convention is mandatory. - const welcome = () => … -> <welcome /> (Wrong!)
- const Welcome = () => … -> <Welcome /> (Correct!)
- Components Must be Pure (Regarding Props): A component must never modify its own props. Props are read-only. All React components must act like pure functions with respect to their props. This ensures that the UI is predictable. If a component needs to change something in response to user input or a network request, it uses state.
Extracting Components
Don’t be afraid to split components into smaller components. If a part of your UI is used several times (like a Button or Panel), or if the UI part itself is complex enough (like a Comment block), it’s a good candidate to be extracted into its own component.
Before Extraction:
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar" src={props.author.avatarUrl} alt={props.author.name} />
<div className="UserInfo-name">{props.author.name}</div>
</div>
<div className="Comment-text">{props.text}</div>
</div>
);
}
After Extraction: We can extract the UserInfo part into an Avatar component and the whole UserInfo block into a UserInfo component.
// 1. Smallest component: Avatar
const Avatar = ({ user }) => {
return <img className="Avatar" src={user.avatarUrl} alt={user.name} />;
};
// 2. Component using Avatar: UserInfo
const UserInfo = ({ user }) => {
return (
<div className="UserInfo">
<Avatar user={user} />
<div className="UserInfo-name">{user.name}</div>
</div>
);
};
// 3. Main component: Comment (now much simpler)
const Comment = (props) => {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">{props.text}</div>
</div>
);
};
This makes each component easier to understand, test, and reuse.
8. React Class
Before React Hooks were introduced in version 16.8, Class Components were the only way to create stateful components in React. While new code should be written with Functional Components and Hooks, understanding Class Components is essential for working on older React projects.
A class component is an ES6 class that extends the React.Component base class.
import React from 'react';
class MyClassComponent extends React.Component {
render() {
return <h1>This is a class component.</h1>;
}
}
The render() Method
The only method you must define in a React.Component subclass is called render(). The render() method is responsible for returning the UI for the component. It should be a “pure” function of this.props and this.state, meaning it should return the same result if the props and state are the same.
State
State is data that is private to a component and can change over time in response to user actions or other events. State is what allows components to be dynamic and interactive.
Initializing State: State is initialized in the constructor() method. The constructor is a special method for creating and initializing an object created with a class. It’s the first thing that runs when a component instance is created.
class Counter extends React.Component {
constructor(props) {
// Always call super(props) first!
super(props);
// Initialize state
this.state = {
count: 0
};
}
render() {
// Access state with `this.state`
return (
<div>
<p>You clicked {this.state.count} times</p>
</div>
);
}
}
Important:
- Always call super(props) before any other statement in the constructor. Otherwise, this.props will be undefined in the constructor.
- State must be a plain JavaScript object.
Updating State with this.setState() You should never mutate state directly, like this.state.count = 1;. React will not know about the change and will not re-render the component.
The only correct way to update state is by using the this.setState() method.
this.setState({ count: this.state.count + 1 });
When this.setState() is called, React knows that the component’s state has changed. It will then schedule a re-render of the component and its children.
setState() is asynchronous. React may batch multiple setState() calls into a single update for performance. Because of this, you should not rely on this.state to calculate the next state. Instead, you can pass a function to setState(). This function will receive the previous state as its first argument.
// Good: Using a function to ensure we have the latest state
this.setState((prevState) => {
return { count: prevState.count + 1 };
});
Component Lifecycle Methods
Class components have “lifecycle methods” that you can override to run code at particular times in the component’s life.
Mounting (Putting into the DOM):
- constructor(): Runs first. Used for initializing state and binding methods.
- render(): Returns the component’s UI.
- componentDidMount(): Runs after the component has been rendered to the DOM. This is the perfect place to make API calls to fetch data, set up subscriptions, or interact with the DOM.
Updating (Re-rendering):
- render(): Called when state or props change.
- componentDidUpdate(): Called immediately after updating occurs. Not called for the initial render. You can perform side effects here, like making network requests based on a change in props.
Unmounting (Removing from the DOM):
- componentWillUnmount(): Called right before a component is removed from the DOM. This is the place to do cleanup, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created in componentDidMount().
Example with Lifecycle Methods:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
// Runs after the component output has been rendered to the DOM.
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
// Runs before the component is removed from the DOM.
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Handling Events
To handle events in class components, you typically define a method on the class and bind it in the constructor. This is necessary to make sure this refers to the component instance inside the event handler.
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isToggleOn: true };
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
Alternatively, you can use a class property with an arrow function to avoid the manual binding in the constructor.
class Toggle extends React.Component {
state = { isToggleOn: true };
// Arrow function automatically binds `this`
handleClick = () => {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
//...
}
9. React Props
Props (short for “properties”) are the way components talk to each other. They are arguments passed into React components, similar to how arguments are passed into a JavaScript function. Props are passed from a parent component down to a child component.
This unidirectional data flow is a core concept in React. It makes applications more predictable and easier to debug.
Passing and Accessing Props
You pass props to a component as attributes in JSX.
// In the parent component (e.g., App.js)
function App() {
return (
<Greeting name="Alice" message="Welcome to React!" />
);
}
// In the child component (e.g., Greeting.js)
function Greeting(props) {
// props is an object: { name: 'Alice', message: 'Welcome to React!' }
return (
<div>
<h1>Hello, {props.name}!</h1>
<p>{props.message}</p>
</div>
);
}
- Functional Components: Props are received as the first (and only) argument to the function. It’s a plain JavaScript object.
- Class Components: Props are available via this.props.
class Greeting extends React.Component {
render() {
// Access props via `this.props`
return (
<div>
<h1>Hello, {this.props.name}!</h1>
<p>{this.props.message}</p>
</div>
);
}
}
Props are Read-Only
This is a fundamental rule in React. A component must never modify its own props. All React components must act like pure functions with respect to their props.
- Pure Function: Given the same input, it will always return the same output and has no side effects.
- Impure Function (what to avoid):
function changeStuff(props) {
// Don't do this! This is a mutation.
props.name = 'Bob';
// ...
}
If a component needs to change something based on user interaction or data fetching, it uses its own internal state. If a child component needs to communicate something back up to the parent, the parent can pass down a function as a prop, which the child can then call.
Default Props
You can define default values for props. If a prop is not provided by the parent component, the component will use the default value instead.
Functional Component with defaultProps:
function Greeting({ name, message }) {
return (
<div>
<h1>Hello, {name}!</h1>
<p>{message}</p>
</div>
);
}
Greeting.defaultProps = {
name: 'Stranger',
message: 'Hello there.'
};
// Usage:
// <Greeting /> will render "Hello, Stranger!" and "Hello there."
// <Greeting name="Alice" /> will render "Hello, Alice!" and "Hello there."
Prop Types for Type Checking
As your application grows, it can be helpful to ensure that components receive props of the correct type. For example, a userID should be a number, and a callback should be a function.
React has a built-in type-checking ability. You can use the prop-types library to define the expected types for your props. This is excellent for development, as React will show a warning in the console if a prop with an invalid type is passed.
First, install the library: npm install prop-types
Then, use it in your component:
import PropTypes from 'prop-types';
function UserProfile({ name, age, isOnline, hobbies, onLogout }) {
// ... component UI
}
UserProfile.propTypes = {
// You can declare that a prop is a specific JS type.
name: PropTypes.string.isRequired, // `isRequired` makes it mandatory.
age: PropTypes.number,
// You can declare that a prop is a boolean.
isOnline: PropTypes.bool,
// You can declare that a prop is a function.
onLogout: PropTypes.func.isRequired,
// An array of a certain type
hobbies: PropTypes.arrayOf(PropTypes.string),
};
While not required, using PropTypes is a highly recommended best practice for building robust and maintainable React applications. It acts as documentation for your components and helps catch bugs early. (Note: In projects using TypeScript, you would use TypeScript’s static types instead of prop-types).
The children Prop
A special, built-in prop is props.children. It contains the content between the opening and closing tags of a component instance. This is very useful for creating generic “box” or “wrapper” components.
// Card component that acts as a generic container
function Card(props) {
return (
<div className="card">
{props.children}
</div>
);
}
// Using the Card component
function App() {
return (
<Card>
{/* Everything inside here is part of `props.children` */}
<h1>Photo</h1>
<img src="photo.jpg" alt="A nice photo" />
<p>This is the description of the photo.</p>
</Card>
);
}
The Card component doesn’t need to know what it will contain. It simply renders whatever children it is given.
10. React Events
Handling events with React elements is very similar to handling events on DOM elements. However, there are some syntactic differences:
- CamelCase Naming: React events are named using camelCase, rather than lowercase. For example, the onclick attribute becomes onClick in React.
- Function Handlers: With JSX, you pass a function as the event handler, rather than a string.
HTML:
<button onclick="showAlert()">Show Alert</button>
React (JSX):
<button onClick={showAlert}>Show Alert</button>
Here, showAlert is a reference to a function. You don’t call it with (). If you did (onClick={showAlert()}), the function would execute immediately when the component renders, not when the button is clicked.
SyntheticEvent
Your event handlers will be passed an instance of SyntheticEvent, which is a cross-browser wrapper around the browser’s native event. It has the same interface as the browser’s native event, including stopPropagation() and preventDefault(), but it works identically across all browsers.
function Form() {
function handleSubmit(e) {
// Prevent the default browser behavior of reloading the page on form submit
e.preventDefault();
console.log('You clicked submit.');
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
Passing Arguments to Event Handlers
Sometimes you need to pass an extra parameter to an event handler. For example, when mapping over a list, you might want to know which item was clicked. You can achieve this by using an arrow function or Function.prototype.bind.
Arrow Function (common approach):
function ItemList({ items }) {
const handleClick = (id) => {
console.log(`Item with id ${id} was clicked.`);
};
return (
<ul>
{items.map((item) => (
<li key={item.id} onClick={() => handleClick(item.id)}>
{item.name}
</li>
))}
</ul>
);
}
Here, we pass an arrow function () => handleClick(item.id) to onClick. When the user clicks the <li>, this arrow function is executed, which in turn calls our handleClick with the correct item.id.
11. React Conditionals
In React, you often want to show or hide elements or components based on the state of your application. For example, you might show a “Log Out” button if the user is authenticated, and “Log In” and “Sign Up” buttons if they are not. This is called conditional rendering.
React’s conditional rendering works the same way conditions work in JavaScript. You can use standard JavaScript operators like if, &&, and the ternary operator (? 🙂 to create elements representing the current state, and React will update the UI to match.
Using if / else
A standard if statement is the most straightforward way to conditionally render a component. This is often used when the logic is more complex or when you want to return a completely different block of JSX.
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <h1>Welcome back!</h1>;
}
return <h1>Please sign up.</h1>;
}
You can also use an if statement to set a variable to a JSX expression and then include that variable in your main JSX block. This is useful for conditionally rendering just a part of a component.
function LoginControl() {
const [isLoggedIn, setIsLoggedIn] = React.useState(false);
// ... event handlers to set isLoggedIn ...
let button;
if (isLoggedIn) {
button = <button>Logout</button>;
} else {
button = <button>Login</button>;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
Inline If with Logical && Operator
For simpler cases where you want to render something only if a condition is true, you can use the JavaScript logical && operator. This is a very common pattern in React.
It works because in JavaScript, true && expression always evaluates to expression, and false && expression always evaluates to false. React doesn’t render false, null, or undefined, so if the condition is false, nothing appears.
function Mailbox({ unreadMessages }) {
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}
// Usage:
// <Mailbox unreadMessages={['a', 'b']} /> -> Renders the h2
// <Mailbox unreadMessages={[]} /> -> Does not render the h2
Inline If-Else with Conditional (Ternary) Operator
Another way to write conditions inline is to use the JavaScript conditional operator condition ? true : false. This is perfect for when you need to choose between two different JSX expressions.
function LoginStatus() {
const [isLoggedIn, setIsLoggedIn] = React.useState(false);
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
}
This can also be used to render different components:
<div>
{isLoggedIn ? <LogoutButton /> : <LoginButton />}
</div>
While powerful, try to avoid nesting ternary operators as they can quickly become difficult to read.
Preventing a Component from Rendering
In some cases, you might want to prevent a component from rendering at all. To do this, you can have it return null.
Returning null from a component’s render method (or from the component function itself) does not affect the firing of the component’s lifecycle methods (or Hooks).
function WarningBanner({ warn }) {
if (!warn) {
return null; // Don't render anything
}
return (
<div className="warning">
Warning!
</div>
);
}
12. React Lists
Displaying lists of data is a fundamental task in web development. In React, you transform arrays of data into lists of elements using standard JavaScript array methods, most commonly .map().
Rendering Lists with .map()
You can build collections of elements and include them in JSX by using curly braces {}. Below, we loop through a numbers array using the JavaScript map() function. We return a <li> element for each item.
const numbers = [1, 2, 3, 4, 5];
function NumberList() {
const listItems = numbers.map((number) =>
<li>{number}</li>
);
return <ul>{listItems}</ul>;
}
This code displays a bulleted list of numbers.
Keys: The Secret to Efficient List Updates
When you run the code above, React will give you a warning: Warning: Each child in a list should have a unique “key” prop.
Keys are a special string attribute you need to include when creating lists of elements. Keys help React identify which items have changed, are added, or are removed. This allows React to perform minimal and efficient updates to the DOM.
A key should be a stable, predictable, and unique identifier. It’s best to use a unique ID from your data, like a database ID.
Let’s fix the previous example by adding keys:
function NumberList() {
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return <ul>{listItems}</ul>;
}
Important Rules for Keys:
- Keys must be unique among siblings. They don’t need to be globally unique. You can use the same keys if they are in different lists (different arrays).
- Keys should be stable. They shouldn’t change between re-renders. Generating a key with Math.random() on every render is an anti-pattern.
Why not use the array index as a key?
It can be tempting to use the item’s index in the array as its key, like map((item, index) => <li key={index}>…). However, this is an anti-pattern and can cause problems with component state and performance.
If the order of list items changes (e.g., due to sorting or adding an item to the beginning of the list), the indexes will also change. React uses the key to match children from the previous render to the next. If the key is the index, a change in order will make React think that all the items have changed, which can lead to it incorrectly re-rendering components and mixing up their state.
Use the index as a key only as a last resort, specifically when:
- The list and items are static–they are not computed and do not change.
- The items in the list have no stable IDs.
- The list is never reordered or filtered.
Rendering a List of Components
The pattern is the same when rendering components. You map over your data and return a component instance for each item, making sure to assign the key to the outermost element in your map.
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
)}
</ul>
);
}
const myTodos = [
{ id: 'a', text: 'Learn JSX' },
{ id: 'b', text: 'Learn about Keys' },
{ id: 'c', text: 'Build a list' }
];
// Usage: <TodoList todos={myTodos} />
Correct Placement of Keys: Keys should be specified inside the map() call on the elements being returned. If you extract a list item into its own component, the key still needs to be on the <ListItem /> element within the array, not on the <li> tag inside the ListItem component.
// Incorrect: Key should be on ListItem
function ListItem({ text }) {
// Wrong! Key is not needed here.
return <li key={text}>{text}</li>;
}
function MyList({ items }) {
return (
<ul>
{items.map((item) =>
// Wrong! The key should have been specified here.
<ListItem text={item.text} />
)}
</ul>
);
}
// Correct: Key is on the element inside the map()
function ListItem({ text }) {
// Correct! Key is not needed here.
return <li>{text}</li>;
}
function MyList({ items }) {
return (
<ul>
{items.map((item) =>
// Correct! The key is specified on the component here.
<ListItem key={item.id} text={item.text} />
)}
</ul>
);
}
13. React Forms
Handling forms in React is different from traditional HTML forms. In HTML, form elements like <input>, <textarea>, and <select> typically maintain their own state and update it based on user input. In React, mutable state is normally kept in the state property of components and updated only with setState().
The React way of handling forms is by making the component’s state the “single source of truth.” A React component that renders a form also controls what happens in that form on subsequent user input. An input form element whose value is controlled by React in this way is called a “controlled component”.
Controlled Components
For a controlled component, the form input’s value is driven by the React state. When the user types into the input, an event handler updates the React state, which then causes the component to re-render, passing the new value back to the input.
Let’s look at a simple example of a controlled text input.
import React, { useState } from 'react';
function NameForm() {
// 1. Create a piece of state to hold the input's value
const [name, setName] = useState('');
// 2. Create an event handler to update the state
const handleChange = (event) => {
// `event.target.value` contains the current value of the input
setName(event.target.value);
};
// 3. Create a handler for form submission
const handleSubmit = (event) => {
alert('A name was submitted: ' + name);
event.preventDefault(); // Prevent page reload
};
return (
<form onSubmit={handleSubmit}>
<label>
Name:
{/* 4. Connect the input to the state */}
<input type="text" value={name} onChange={handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
The flow is:
- The <input>’s value is set from the name state.
- When the user types, the onChange event fires.
- The handleChange function is called, which calls setName() with the new value.
- The state is updated, and the component re-renders.
- The input now displays the new value from the updated state.
This makes it easy to access the input’s value (it’s always in this.state or your useState variable) and validate or manipulate it on the fly.
The textarea Tag
In HTML, a <textarea> element’s text is set by its children: <textarea>Some text</textarea>. In React, a <textarea> uses a value attribute instead. This makes it work just like a single-line text input.
function EssayForm() {
const [value, setValue] = useState('Please write an essay about your favorite DOM element.');
const handleChange = (event) => {
setValue(event.target.value);
};
return (
<form>
<label>
Essay:
<textarea value={value} onChange={handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
The select Tag
In HTML, the selected option in a <select> tag is specified by the selected attribute on an <option>. In React, to make it a controlled component, you set a value attribute on the root <select> tag. This is more convenient because you only need to update the state in one place.
function FlavorForm() {
const [value, setValue] = useState('coconut'); // Default value
const handleChange = (event) => {
setValue(event.target.value);
};
return (
<form>
<label>
Pick your favorite flavor:
<select value={value} onChange={handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
Handling Multiple Inputs
When you have multiple controlled inputs, you can add a name attribute to each element and let the handler function choose what to do based on the value of event.target.name.
A common pattern is to have a single state object that holds the values for all inputs.
function Reservation() {
const [formState, setFormState] = useState({
isGoing: true,
numberOfGuests: 2
});
const handleInputChange = (event) => {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
// Use the input's `name` to update the correct piece of state
setFormState({
...formState, // Copy existing state
[name]: value // Update the specific field
});
};
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={formState.isGoing}
onChange={handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={formState.numberOfGuests}
onChange={handleInputChange} />
</label>
</form>
);
}