Reusability of Components in ReactJS
This documentation covers the concept of reusability of components in ReactJS, explaining why it's important, how to create reusable components, and best practices for maintaining reusability in your applications.
Introduction to Reusability
What is Reusability in ReactJS?
Imagine you are building a beautiful piece of furniture, and you need to use the same type of screws and bolts in several parts of the structure. Instead of crafting each screw and bolt by hand every time you need one, you manufacture them once and use them where needed. This saves time, reduces errors, and ensures consistency.
In the context of building web applications with ReactJS, reusability works similarly. Reusable components allow you to create a piece of UI (like a button or a form) once and use it multiple times across different parts of your application or even in different applications.
Why is Reusability Important?
-
Efficiency: Writing the same code multiple times is inefficient. Reusability helps in saving time and effort.
-
Consistency: When you use the same component in various places, it ensures that the look and behavior remain consistent across your application.
-
Maintainability: If you need to change something, you only have to make that change in one place rather than many.
-
Scalability: Reusable components allow your application to grow in complexity without the codebase becoming overwhelmingly difficult to manage.
Basic Concepts of Reusable Components
Identifying Reusable Code
Let's think of the UI of a blog. You might have a components like a NavBar
, Footer
, Comment
, or PostList
that are used across different pages. These are good candidates for being turned into reusable components.
Creating Simple Reusable Components
Creating reusable components in ReactJS is as straightforward as creating standalone pieces of code that can be imported and exported between different parts of your app.
// src/components/Button.js
import React from 'react';
function Button() {
return (
<button style={{ padding: '10px 20px', background: 'blue', color: 'white' }}>
Click Me
</button>
);
}
export default Button;
In this simple example, we created a Button
component. This component can be reused in multiple parts of your application by importing it wherever needed.
// src/App.js
import React from 'react';
import Button from './components/Button';
function App() {
return (
<div>
<h1>Welcome to My Blog</h1>
<Button />
<Button />
</div>
);
}
export default App;
Here, we import the Button
component and use it twice in the App
component. This demonstrates the simplicity and power of reusability in ReactJS.
Props for Component Reusability
Understanding Props in ReactJS
Props (short for properties) are a way to pass data from one component to another. Think of props as parameters for HTML elements. You can use them to make your components flexible and reusable.
Passing Props to Components
Let’s enhance our Button
component to accept props.
// src/components/Button.js
import React from 'react';
function Button(props) {
return (
<button style={{ padding: '10px 20px', background: 'blue', color: 'white' }}>
{props.label}
</button>
);
}
export default Button;
In this example, the Button
component now displays a label passed through props.
// src/App.js
import React from 'react';
import Button from './components/Button';
function App() {
return (
<div>
<h1>Welcome to My Blog</h1>
<Button label="Read More" />
<Button label="Subscribe" />
</div>
);
}
export default App;
Here, we pass different labels to our Button
component, displaying "Read More" on the first button and "Subscribe" on the second, showcasing how props enable you to make components more flexible and reusable.
Using Functional and Class Components with Props
Both functional and class components can receive and use props.
Functional Components
// src/components/Button.js
import React from 'react';
function Button(props) {
return (
<button style={{ padding: '10px 20px', background: 'blue', color: 'white' }}>
{props.label}
</button>
);
}
export default Button;
Class Components
// src/components/Button.js
import React, { Component } from 'react';
class Button extends Component {
render() {
return (
<button style={{ padding: '10px 20px', background: 'blue', color: 'white' }}>
{this.props.label}
</button>
);
}
}
export default Button;
Both examples demonstrate that you can pass props to both functional and class components.
State Management in Reusable Components
What is State?
In ReactJS, state is an object that stores a part of the component's data that can change over time. State is owned by the component and can be modified using the setState
method in class components or the useState
hook in functional components.
Managing State in Reusable Components
Let's create a counter component to understand how state management can be incorporated with reusability.
Functional Component Example
// src/components/Counter.js
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Counter;
Class Component Example
// src/components/Counter.js
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
incrementCount = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.incrementCount}>
Click me
</button>
</div>
);
}
}
export default Counter;
Both examples allow the Counter
component to maintain its own state and increment it when a button is clicked. This component can now be reused wherever you need a counter in your application.
Lift State Up for Reusable Component Patterns
Sometimes, you might need to share state between components. In such cases, lifting the state up (moving it to their common parent) is a good practice.
// src/components/SiblingCounter.js
import React, { useState } from 'react';
function SiblingCounter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Current Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
export default SiblingCounter;
// src/App.js
import React from 'react';
import Counter from './components/Counter';
import SiblingCounter from './components/SiblingCounter';
function App() {
const [sharedCount, setSharedCount] = useState(0);
return (
<div>
<h1>Welcome to My Blog</h1>
<Counter />
<SiblingCounter count={sharedCount} setCount={setSharedCount} />
</div>
);
}
export default App;
In this example, App
component manages the sharedCount
state and passes it down to SiblingCounter
. This allows multiple components to share and update state.
Event Handling in Reusable Components
Basics of Event Handling
Handling events in React is similar to handling events in vanilla JavaScript, but with a more declarative approach. Instead of attaching listeners to the DOM directly, you provide a function to the event handler attribute.
Passing Events as Props
You can pass functions as props to other components, allowing them to handle events.
// src/components/ReusableButton.js
import React from 'react';
function ReusableButton(props) {
return (
<button onClick={props.onClick}>
{props.label}
</button>
);
}
export default ReusableButton;
// src/App.js
import React from 'react';
import ReusableButton from './components/ReusableButton';
function App() {
const handleClick = () => {
alert('Button clicked!');
};
return (
<div>
<h1>Welcome to My Blog</h1>
<ReusableButton label="Click Me" onClick={handleClick} />
</div>
);
}
export default App;
Here, the handleClick
function defined in App
is passed to ReusableButton
through props.
Handling Events in Reusable Components
You can also define functions inside the component that handle events.
// src/components/ReusableButton.js
import React from 'react';
function ReusableButton() {
const handleClick = () => {
alert('Button clicked!');
};
return (
<button onClick={handleClick}>
Click Me
</button>
);
}
export default ReusableButton;
In this example, the handleClick
function is defined inside ReusableButton
.
Conditional Rendering in Reusable Components
Introduction to Conditional Rendering
Conditional rendering in React is about rendering different UI components based on certain conditions.
Using Conditional Rendering in Reusable Components
Let's create a reusable Message
component that displays different messages based on a condition.
// src/components/Message.js
import React from 'react';
function Message(props) {
const { isLoggedIn } = props;
return (
<div>
{isLoggedIn ? <p>Welcome Back!</p> : <p>Please Log In</p>}
</div>
);
}
export default Message;
// src/App.js
import React, { useState } from 'react';
import Message from './components/Message';
function App() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div>
<h1>Welcome to My Blog</h1>
<Message isLoggedIn={isLoggedIn} />
<button onClick={() => setIsLoggedIn(!isLoggedIn)}>
Toggle Login
</button>
</div>
);
}
export default App;
In this example, the Message
component renders different messages based on the isLoggedIn
prop.
Lists and Keys in Reusable Components
Rendering Lists
Rendering lists in React is straightforward. You can render lists by map()ping over an array and returning a component for each element.
// src/components/ItemList.js
import React from 'react';
function ItemList(props) {
const items = props.items;
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
export default ItemList;
// src/App.js
import React from 'react';
import ItemList from './components/ItemList';
function App() {
const items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' }
];
return (
<div>
<h1>Welcome to My Blog</h1>
<ItemList items={items} />
</div>
);
}
export default App;
In this example, we pass an array of items to ItemList
and render each item in a list.
Using Keys in Lists
When rendering lists, React uses keys to identify which items have changed, are added, or are removed. Keys should be unique among siblings and are typically given to elements in iterated arrays.
Best Practices for Keys in Lists
- Use unique identifiers for keys whenever possible (e.g., IDs from a database).
- Avoid using array indices as keys unless the list and order never changes, as it can lead to performance issues and subtle bugs.
Handling Forms in Reusable Components
Form Basics in ReactJS
Forms in React work similarly to forms in vanilla JavaScript, but instead of using the DOM to find the form data, you would use state to keep track of the form data and the state is the single source of truth.
Controlling Form Elements
Let's create a reusable form component.
// src/components/Form.js
import React, { useState } from 'react';
function Form(props) {
const [inputValue, setInputValue] = useState('');
const handleChange = (event) => {
setInputValue(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
props.onSubmit(inputValue);
setInputValue('');
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={inputValue}
onChange={handleChange}
placeholder="Enter text"
/>
<button type="submit">Submit</button>
</form>
);
}
export default Form;
// src/App.js
import React from 'react';
import Form from './components/Form';
function App() {
const handleSubmit = (value) => {
alert(`Submitted value: ${value}`);
};
return (
<div>
<h1>Welcome to My Blog</h1>
<Form onSubmit={handleSubmit} />
</div>
);
}
export default App;
Here, the Form
component manages its own state (the input value) but handles the form submission by calling a function passed through props (onSubmit
).
Form Submission Handling in Reusable Components
In the example above, the handleSubmit
function in the Form
component calls the onSubmit
function passed as a prop, passing the input value as an argument. This pattern keeps the form component reusable while allowing external components to define what happens on form submission.
Styling Reusable Components
Inline Styling
You can use inline styles directly in your components.
// src/components/Button.js
import React from 'react';
function Button(props) {
const buttonStyle = {
padding: '10px 20px',
background: 'blue',
color: 'white',
};
return (
<button style={buttonStyle} onClick={props.onClick}>
{props.label}
</button>
);
}
export default Button;
This approach is useful for quick styling but can become difficult to manage for larger applications.
CSS Classes
You can also use regular CSS classes.
/* src/components/Button.css */
.button {
padding: 10px 20px;
background: blue;
color: white;
border: none;
cursor: pointer;
}
.button:hover {
background: darkblue;
}
// src/components/Button.js
import React from 'react';
import './Button.css';
function Button(props) {
return (
<button className="button" onClick={props.onClick}>
{props.label}
</button>
);
}
export default Button;
CSS Modules and SCSS
For larger applications, CSS Modules and SCSS are recommended to avoid class name conflicts.
/* src/components/Button.module.scss */
.button {
padding: 10px 20px;
background: blue;
color: white;
border: none;
cursor: pointer;
&:hover {
background: darkblue;
}
}
// src/components/Button.js
import React from 'react';
import styles from './Button.module.scss';
function Button(props) {
return (
<button className={styles.button} onClick={props.onClick}>
{props.label}
</button>
);
}
export default Button;
Higher Order Components (HOC)
What is HOC?
A Higher Order Component (HOC) is a strategy for code reuse in React. HOCs are not a part of the React API but a pattern derived from React’s compositional nature. A HOC is a function that takes a component and returns a new component.
Creating and Using HOCs
// src/components/withUser.js
import React, { Component } from 'react';
function withUser(WrappedComponent) {
class EnhancedComponent extends Component {
render() {
return <WrappedComponent name="John Doe" {...this.props} />;
}
}
return EnhancedComponent;
}
export default withUser;
// src/components/Greeting.js
import React from 'react';
function Greeting(props) {
return <p>Hello, {props.name}!</p>;
}
export default Greeting;
// src/App.js
import React from 'react';
import withUser from './components/withUser';
import Greeting from './components/Greeting';
const GreetingWithUser = withUser(Greeting);
function App() {
return (
<div>
<h1>Welcome to My Blog</h1>
<GreetingWithUser />
</div>
);
}
export default App;
In this example, withUser
is a HOC that takes a WrappedComponent
as an argument and returns a new component that includes a name
prop.
Use Cases for HOCs
- Code Injection: Injecting common functionality (like fetching data) into components.
- Theme Management: Providing a theme to components through props.
- Permissions and Authentication: Controlling component rendering based on user permissions.
Custom Hooks for Reusability
Introduction to Custom Hooks
Custom Hooks allow you to extract component logic into hook functions. When you have component logic that needs to be shared between different components, extract that logic to a custom Hook.
Creating Custom Hooks
// src/hooks/useCounter.js
import React, { useState } from 'react';
function useCounter(initialCount = 0) {
const [count, setCount] = useState(initialCount);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return { count, increment, decrement };
}
export default useCounter;
// src/components/Counter.js
import React from 'react';
import useCounter from '../hooks/useCounter';
function Counter() {
const { count, increment, decrement } = useCounter();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
export default Counter;
Use Cases for Custom Hooks
- Stateful Logic: Sharing stateful logic between different components.
- API Calls: Managing API calls and data fetching.
- Side Effects: Managing side effects using the
useEffect
hook.
Composing Reusable Components
Combining Multiple Components
Combine multiple components to build even more complex and reusable UI.
// src/components/Card.js
import React from 'react';
function Card(props) {
return (
<div className="card">
<h3>{props.title}</h3>
<p>{props.content}</p>
</div>
);
}
export default Card;
// src/components/Article.js
import React from 'react';
import Card from './Card';
function Article() {
return (
<div>
<h2>My Article</h2>
<Card title="Introduction" content="This is the introduction of the article." />
<Card title="Conclusion" content="This is the conclusion of the article." />
</div>
);
}
export default Article;
In this example, the Card
component is used to display different parts of an article.
Nested Reusable Components
Components can be nested within each other to create complex UIs.
// src/components/NestedCard.js
import React from 'react';
import Card from './Card';
function NestedCard() {
return (
<Card title="Nested Card">
<Card title="Inner Card" content="This is an inner card." />
</Card>
);
}
export default NestedCard;
Component Composition Techniques
Component composition is about building complex UIs from smaller, reusable components.
// src/components/Layout.js
import React from 'react';
function Layout(props) {
return (
<div className="layout">
<header>
<h1>My Blog</h1>
</header>
<main>
{props.children}
</main>
<footer>
<p>© 2023 My Blog</p>
</footer>
</div>
);
}
export default Layout;
// src/App.js
import React from 'react';
import Layout from './components/Layout';
function App() {
return (
<Layout>
<h2>Welcome to My Blog</h2>
<p>This is the main content of the blog.</p>
</Layout>
);
}
export default App;
In this example, the Layout
component is used to wrap the main content, providing a consistent layout structure across different pages or sections of the application.
Best Practices for Reusability
Code Splitting
Code splitting is the process of dividing your application into smaller chunks which can be loaded on demand or in parallel. This can significantly improve the performance of your app.
Avoiding Prop Drilling
Prop drilling occurs when you pass props through many levels of components just to get data to where it needs to go. Using context or state management libraries (like Redux) can help avoid prop drilling.
Refactoring for Reusability
Refactoring your components to be more reusable involves extracting common functionality into separate components and components that manage their own state, making them independent and reusable.
// src/components/ReusableInput.js
import React, { useState } from 'react';
function ReusableInput(props) {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
props.onChange(value);
};
return (
<input
type="text"
value={value}
onChange={handleChange}
placeholder={props.placeholder}
/>
);
}
export default ReusableInput;
// src/App.js
import React from 'react';
import ReusableInput from './components/ReusableInput';
function App() {
const handleInputChange = (value) => {
console.log('Input value:', value);
};
return (
<div>
<h1>Welcome to My Blog</h1>
<ReusableInput placeholder="Enter your name" onChange={handleInputChange} />
</div>
);
}
export default App;
In this example, the ReusableInput
component manages its own state but allows its parent to handle changes via props.
Wrapping Up
Understanding and implementing reusability in ReactJS is key to building maintainable and scalable applications. By using props, state, event handling, conditional rendering, and other patterns, you can create components that are highly reusable and adaptable to various use cases in your application.