Mastering React Components: An In-Depth Guide

Exploring the core concepts of React components, best practices for building reusable components, and understanding their key features.

Ben Mishali
Bits and Pieces

--

React, a popular JavaScript library for building user interfaces, was developed by Facebook in 2013. With its declarative nature and component-based architecture, React enables developers to create scalable, fast, and maintainable applications. It is used for building complex applications, such as web apps, mobile apps, and even virtual reality experiences.

Introduction

In this article, we will dive into the world of React components, exploring their core concepts, best practices for building reusable components, and understanding their key features, such as states and props. We will also provide code examples to illustrate these concepts, helping you become a master of React components.

React Components: The Building Blocks

Components are the fundamental building blocks of a React application — Just like LEGO. They allow you to create reusable, modular pieces of code that can be combined and nested to create complex UI structures. Components are essentially JavaScript functions or classes that return a React element, which describes the UI to be rendered.

💡 Pro Tip: For building React applications just like LEGO, you could consider using tools such as Bit. With Bit you can pack your React components into packages and use them across multiple projects with a simple npm i @bit/your-username/your-component.

Learn More:

A React component can be either a class component or a functional component. Class components were the standard approach in earlier versions of React but have been largely replaced by functional components with the introduction of React hooks.

Here’s a simple example of a functional component:

function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

Gibberish ??? Don’t worry, everything will be explained later.

Understanding States and Props

States and props are two key concepts when working with React components. They allow you to manage data flow and communication between components.

Props

Props (short for properties) are the way you pass data from a parent component to a child component. They are read-only and cannot be modified by the child component.

Example of passing props:

function ParentComponent() {
return <ChildComponent name="John Doe" />;
}

function ChildComponent(props) {
return <h1>Hello, {props.name}</h1>;
}

State

State represents the local state of a component, which can change over time. State is managed within the component and can be used to render dynamic content based on user interactions or other events.

Example of using state with hooks:

import React, { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Current count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

Component Lifecycle

React components go through a series of lifecycle events from creation to deletion.

Some of the most important lifecycle methods are:

componentDidMount

Called once when the component is first mounted to the DOM. b.

componentDidUpdate

Called after a component’s state or props have been updated. c.

componentWillUnmount

Called just before a component is removed from the DOM.

Understanding these lifecycle events allows you to optimize performance and manage side effects within your components.

Advanced React Component Patterns

As you become more comfortable with React components, you might want to explore advanced patterns for structuring and managing your components. Some popular advanced patterns include:

Higher-Order Components (HOCs)

HOCs are functions that take a component and return a new component with additional props or behavior. They are a useful way to reuse component logic across your application.

Example of a simple HOC:

function withLogging(WrappedComponent) {
return function LoggingComponent(props) {
console.log('Rendering', WrappedComponent.name);
return <WrappedComponent {...props} />;
};
}

const LoggedWelcome = withLogging(Welcome);

function App() {
return <LoggedWelcome name="John Doe" />;
}

Render Props

Render props are a technique for sharing code between components using a prop whose value is a function. They allow you to create flexible, reusable components without using higher-order components or mixins.

Example of a render prop component:

function MouseTracker(props) {
const [position, setPosition] = useState({ x: 0, y: 0 });

function handleMouseMove(event) {
setPosition({ x: event.clientX, y: event.clientY });
}
return (
<div style={{ height: '100%' }} onMouseMove={handleMouseMove}>
{props.render(position)}
</div>
);
}

function App() {
return (
<MouseTracker
render={({ x, y }) => (
<p>
The current mouse position is ({x}, {y})
</p>
)}
/>
);
}

Context API

The Context API allows you to share data across the component tree without passing props down manually through every level. It’s useful for sharing global data, such as themes or user authentication data.

Example of using the Context API:

import React, { useContext, useState } from 'react';

const ThemeContext = React.createContext();

function ThemeProvider(props) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{props.children}
</ThemeContext.Provider>
);
}

function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
function toggleTheme() {
setTheme(theme === 'light' ? 'dark' : 'light');
}
return (
<button onClick={toggleTheme}>
Toggle theme (current: {theme})
</button>
);
}

function App() {
return (
<ThemeProvider>
<ThemedButton />
</ThemeProvider>
);
}

By understanding and implementing these advanced patterns, you can further enhance the reusability, maintainability, and scalability of your React applications. As you continue to explore and practice with React components, you will develop a deeper understanding of these patterns and their use cases, ultimately helping you create more efficient and powerful applications.

Optimizing React Components

As your React applications grow in complexity, it’s essential to optimize your components to ensure optimal performance. This section will cover some key techniques for optimizing React components and reducing unnecessary re-renders.

Memoization

Memoization is a technique that allows you to cache the results of expensive function calls or component renders. React provides a built-in React.memo() function that can be used to memoize functional components.

Example of using React.memo():

const ExpensiveComponent = React.memo(function ExpensiveComponent(props) {
// This component will only re-render if its props change.
return <div>{/* Expensive computation */}</div>;
});

useMemo and useCallback hooks

The useMemo and useCallback hooks allow you to memoize values and callbacks within your components, reducing unnecessary recalculations and re-renders.

Example of using useMemo and useCallback:

import React, { useMemo, useCallback } from 'react';

function ParentComponent() {
const [count, setCount] = useState(0);
const increment = useCallback(() => setCount(count + 1), [count]);
// Only recompute the list if the count changes.
const items = useMemo(() => getItems(count), [count]);
return (
<div>
<button onClick={increment}>Increment</button>
<List items={items} />
</div>
);
}

Using shouldComponentUpdate or PureComponent

In class components, you can use the shouldComponentUpdate lifecycle method to determine if a component should re-render based on changes in its props and state. Alternatively, you can extend React.PureComponent instead of React.Component to have an automatic shallow comparison of props and state.

Example of using shouldComponentUpdate:

class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// Only update if relevant props or state have changed.
return (
nextProps.someProp !== this.props.someProp ||
nextState.someState !== this.state.someState
);
}

render() {
return <div>{/* ... */}</div>;
}
}

Virtualized lists

When rendering large lists or tables, it’s essential to optimize rendering performance by only rendering the visible items. Libraries such as react-window or react-virtualized can help you achieve this.

Example of using react-window:

import { FixedSizeList as List } from 'react-window';

function MyList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>{items[index]}</div>
);
return (
<List
height={500}
itemCount={items.length}
itemSize={35}
width={800}
>
{Row}
</List>
);
}

By applying these optimization techniques, you can ensure your React components and applications perform efficiently even as they scale. Continually profiling and optimizing your components will help you maintain a smooth user experience and prevent performance bottlenecks.

Fetch data from API with useEffect Hook

useEffect hook

useEffect is a built-in hook in React that allows you to perform side effects, such as fetching data, subscribing to an event, or updating the DOM, in functional components. Side effects are actions that can affect the outside world or depend on the outside world.

The useEffect hook takes two arguments:

  1. A function called the “effect function”, which contains the side effect you want to perform.
  2. An optional array of dependencies, which is a list of values that the effect depends on. If any of the dependencies change, the effect function will be re-run.

The useEffect hook is called after the component renders, and it is executed every time the component updates if no dependency array is provided or if any values in the dependency array have changed.

In this example, we’ll fetch weather data from the OpenWeatherMap API and display the results in a React component as a list. First, you’ll need to sign up for an API key at https://openweathermap.org/api.

Let’s create a new functional component called WeatherList:

import React, { useState, useEffect } from 'react';

function WeatherList() {
const [weatherData, setWeatherData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const API_KEY = 'your_api_key_here'; // Replace with your OpenWeatherMap API key.
useEffect(() => {
async function fetchWeatherData() {
setLoading(true);
try {
const response = await fetch(
`https://api.openweathermap.org/data/2.5/find?q=London&units=metric&appid=${API_KEY}`
);
const data = await response.json();
setWeatherData(data.list);
setError(null);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
}
fetchWeatherData();
}, []);
if (loading) {
return <div>Loading...</div>;
}
if (error) {
return <div>Error: {error.message}</div>;
}
return (
<div>
<h1>Weather List</h1>
<ul>
{weatherData.map((item) => (
<li key={item.id}>
{item.name}: {item.main.temp}°C
</li>
))}
</ul>
</div>
);
}
export default WeatherList;

In this example, we define a WeatherList component that uses the useState and useEffect hooks to manage its state and fetch weather data from the OpenWeatherMap API. The useEffect hook is used to run side effects, such as fetching data, after the component has rendered.

The fetchWeatherData function, which is defined inside the useEffect hook, is an async function that fetches weather data from the OpenWeatherMap API using the provided API key. The fetched data is then saved to the local weatherData state using the setWeatherData function.

The loading state is used to track whether the component is currently fetching data, and the error state is used to track any errors that occurred during the fetch operation. The component's UI is updated based on these states to show a loading indicator, an error message, or the fetched weather data as a list.

To use the WeatherList component in your application, you can simply include it in your component tree, like this:

import React from 'react';
import WeatherList from './WeatherList';

function App() {
return (
<div>
<h1>Weather App</h1>
<WeatherList />
</div>
);
}
export default App;

Top Tips for Building Reusable React Components

  1. Keep components small and focused: Create components that have a single responsibility and do one thing well. This makes them easier to understand, maintain, and reuse.
  2. Use PropTypes: PropTypes allow you to define the expected types of properties passed to a component. This helps ensure that components are used correctly and makes your code more robust and maintainable.
  3. Use functional components and hooks: React hooks simplify the code and make it easier to reason about. They allow you to use state and other React features in functional components, making them more reusable and easier to test.
  4. Embrace composition: Break down complex components into smaller, more manageable ones. This makes your codebase more modular and allows for greater reusability.
  5. Make components configurable: Allow users to pass in props to customize the appearance and behavior of your components. This helps make them more flexible and reusable across different use cases.

Conclusion

Mastering React components is an ongoing journey, as you explore different patterns, techniques, and best practices. As you gain more experience, you will develop a better understanding of when to use certain techniques and how to structure your components effectively. Keep experimenting and learning, and you will continue to grow as a React developer, building powerful and efficient applications.

Build React Apps with reusable components, just like Lego

Bit’s open-source tool help 250,000+ devs to build apps with components.

Turn any UI, feature, or page into a reusable component — and share it across your applications. It’s easier to collaborate and build faster.

Learn more

Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:

Micro-Frontends

Design System

Code-Sharing and reuse

Monorepo

Visit me at ben.dev.io

--

--

𝔹𝕖𝕟 | 𝕊𝕠𝕗𝕥𝕨𝕒𝕣𝕖 𝔼𝕟𝕘𝕚𝕟𝕖𝕖𝕣 🕵‍♂️ Technologies Explorer 👨🏻‍💻 Web & Native developer 🤓 Sharing my knowledge and experiences