React Components (Web Development)
Learn React Components (Web Development) step by step with clear examples and exercises.
Why This Matters
React components are a crucial part of modern web development, as they allow developers to create scalable, efficient, and maintainable user interfaces (UI) using JavaScript. By understanding React components, you can build complex applications with ease by combining smaller, reusable parts.
Prerequisites
To fully grasp the concepts covered in this lesson, you should have a basic understanding of the following topics:
- HTML: The structure and syntax of HTML documents.
- CSS: Styling web pages using cascading style sheets.
- JavaScript (ES6): Basic concepts such as variables, functions, arrays, objects, and event handling.
- NPM (Node Package Manager): Managing project dependencies with npm.
- Babel: Transpiling modern JavaScript code to be compatible with older browsers.
- Familiarity with a text editor or Integrated Development Environment (IDE) for writing and editing code.
- Basic understanding of the React ecosystem, including JSX syntax, component lifecycle, and state management.
Core Concept
What are React Components?
React components are reusable pieces of code that represent a part of a UI. They encapsulate logic, markup, and styles, making it easier to build complex UIs by combining smaller, independent parts. A simple example is a Button component that displays a clickable button with some text:
function Button(props) {
const { children, onClick } = props;
return (
<button onClick={onClick}>
{children}
</button>
);
}
In this example, the Button component takes in a props object that contains the onClick event handler and any children elements (text or other components). The component returns a button element with the given properties.
Component Lifecycle
React components have a lifecycle that consists of three main phases: mounting, updating, and unmounting. During each phase, specific methods are called to manage the component's state and interactions with other components or external data sources.
- Mounting: When a component is first added to the DOM, its constructor method initializes the state, followed by the
render()method, which generates the component's markup. Finally, thecomponentDidMount()method is called to perform any necessary setup or API calls.
- Updating: When a component's state or props change, it re-renders and updates its DOM representation. The
shouldComponentUpdate(),render(), andcomponentDidUpdate()methods are involved in this process.
- Unmounting: When a component is removed from the DOM, the
componentWillUnmount()method is called to clean up any resources or event listeners that were created during the component's lifetime.
State and Props
State and props are data objects used to manage a component's internal state and receive external data, respectively. The primary difference between them is that state is local to a single component, while props can be passed down from parent components.
- State: Managed using the
this.stateobject, which should be initialized in the constructor method and updated using thesetState()function.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
handleClick = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<button onClick={this.handleClick}>{this.state.count}</button>
</div>
);
}
}
- Props: Passed down from parent components using the
propsobject, which is read-only for child components. Props can be used to customize a component's behavior or appearance based on external data.
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
ReactDOM.render(<Greeting name="Alice" />, document.getElementById('root'));
Component Hierarchy and Composition
React components can be organized into a tree-like hierarchy, with parent components passing props to child components. This allows for complex UIs to be built by combining multiple smaller components. Additionally, higher-order components (HOCs) can be used to reuse logic across multiple components or modify their behavior at runtime.
function withCounter(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
handleClick = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<WrappedComponent
{...this.props}
count={this.state.count}
handleClick={this.handleClick}
/>
);
}
};
}
const CounterButton = withCounter(({ count, handleClick }) => (
<button onClick={handleClick}>{count}</button>
));
Worked Example
Let's create a simple TodoList component that displays a list of tasks and allows users to add and remove tasks.
- First, install the required dependencies:
npx create-react-app todo-list
cd todo-list
npm install --save react-bootstrap bootstrap
- Create a new file
src/TodoList.jsand implement the component:
import React, { useState } from 'react';
import { ListGroup, ListGroupItem } from 'react-bootstrap';
function TodoList() {
const [tasks, setTasks] = useState([]);
function addTask(task) {
setTasks([...tasks, task]);
}
function removeTask(index) {
const newTasks = [...tasks];
newTasks.splice(index, 1);
setTasks(newTasks);
}
return (
<ListGroup>
{tasks.map((task, index) => (
<ListGroupItem key={index} action onClick={() => removeTask(index)}>
{task}
</ListGroupItem>
))}
<form onSubmit={e => {
e.preventDefault();
const input = document.getElementById('new-task');
addTask(input.value);
input.value = '';
}}>
<input type="text" id="new-task" />
<button type="submit">Add Task</button>
</form>
</ListGroup>
);
}
export default TodoList;
- Update
src/App.jsto use theTodoListcomponent:
import React from 'react';
import './App.css';
import TodoList from './TodoList';
function App() {
return (
<div className="App">
<h1>My Todo List</h1>
<TodoList />
</div>
);
}
export default App;
- Finally, start the development server:
npm start
Common Mistakes
- Forgetting to bind event handlers: In class components, event handlers should be bound within the constructor method using
this. For example:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('Button clicked!');
}
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}
- Misusing state: Avoid mutating the state directly, as it can lead to unexpected behavior. Instead, use
setState()to update the state:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
handleClick = () => {
// Incorrect: this.state.count++;
this.setState({ count: this.state.count + 1 });
}
render() {
return <button onClick={this.handleClick}>{this.state.count}</button>;
}
}
- Not using keys in lists: Each item in a list should have a unique key prop to help React efficiently update the DOM:
function MyList(props) {
return (
<ul>
{props.items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
}
Practice Questions
- Create a
Countercomponent that displays the current count and two buttons: one for incrementing the count, and another for decrementing it. Use state to manage the count.
- Implement a
Formcomponent that takes in a name and an email as props, and returns a form with fields for both. The form should have a submit button that logs the entered data to the console when submitted.
- Create a
Modalcomponent that displays a message and an OK button. The modal should be closed when the user clicks the OK button or outside the modal area.
FAQ
- What is the difference between state and props in React components?
- State: Local data managed by a single component, updated using
setState(). - Props: External data passed down from parent components to child components, read-only for child components.
- Why should I use keys in lists of elements in React?
- Keys help React identify which items have changed, are added, or are removed in the list, improving the performance of updates.
- What is a higher-order component (HOC) in React?
- A HOC is a function that takes another component and returns a new component with additional functionality or behavior. This allows for code reuse and modularization.