Understanding Reusable Components and the DRY Principle
Reusable components and the DRY principle (Don't Repeat Yourself) are essential to developing effective, maintainable software, especially in development. This post will examine these ideas, discuss their significance, and offer useful React code examples to show readers how to put them to use.
What are Reusable Components?
In React, reusable components are UI building blocks that can be used multiple times across your application. Instead of creating a similar structure repeatedly, you abstract the shared logic, structure, and design into a single component, which can be reused with slight variations.
Here are a few examples of reusable components in React:
1. Button Component
Instead of creating separate buttons for login, signup, or any other actions, you can create a generic button component that accepts props for customization:
import React from "react";
function Button({ text, styleClass, onClick }) {
return (
<button className={styleClass} onClick={onClick}>
{text}
</button>
);
}
export default Button;
Usage:
<Button text="Login" styleClass="btn-primary" onClick={handleLogin} />
<Button text="Signup" styleClass="btn-secondary" onClick={handleSignup} />
In this case, the Button
component is reusable across different parts of your application by passing different text
, styleClass
, and onClick
handlers as props.
2. Input Field Component
Similarly, a reusable input field component can be used for both login and signup forms, or any other form in the application:
function Input({ type, placeholder, value, onChange }) {
return (
<input
type={type}
placeholder={placeholder}
value={value}
onChange={onChange}
/>
);
}
export default Input;
Usage:
<Input
type="text"
placeholder="Enter your username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<Input
type="password"
placeholder="Enter your password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
3. Form Component
Taking this concept further, you can create a reusable form component that handles various forms (e.g., login, signup, reset password) by dynamically rendering fields and buttons based on the form type:
import React from "react";
import Input from "./Input";
import Button from "./Button";
function Form({ fields, onSubmit, buttonText }) {
return (
<form onSubmit={onSubmit}>
{fields.map((field, index) => (
<Input
key={index}
type={field.type}
placeholder={field.placeholder}
value={field.value}
onChange={field.onChange}
/>
))}
<Button text={buttonText} styleClass="btn-primary" />
</form>
);
}
export default Form;
Usage for login:
<Form
fields={[
{
type: "text",
placeholder: "Username",
value: username,
onChange: (e) => setUsername(e.target.value),
},
{
type: "password",
placeholder: "Password",
value: password,
onChange: (e) => setPassword(e.target.value),
},
]}
onSubmit={handleLoginSubmit}
buttonText="Login"
/>
And for signup:
<Form
fields={[
{
type: "text",
placeholder: "Username",
value: username,
onChange: (e) => setUsername(e.target.value),
},
{
type: "email",
placeholder: "Email",
value: email,
onChange: (e) => setEmail(e.target.value),
},
{
type: "password",
placeholder: "Password",
value: password,
onChange: (e) => setPassword(e.target.value),
},
]}
onSubmit={handleSignupSubmit}
buttonText="Signup"
/>
Benefits of Reusable Components
By creating components like Button
, Input
, and Form
, you make your code:
Consistent: Reusable components ensure that buttons, forms, and input fields behave and look the same throughout your application.
Maintainable: When you need to update styles or add new functionality, you only need to change the component once, and the changes will reflect across your app.
Efficient: Instead of duplicating code, you save time by reusing the same components with minor adjustments through props.
The DRY Principle: Don't Repeat Yourself
I'll go over every step of this DRY example utilizing React as we go through it. We'll look at an example where we follow the DRY principle and use reusable components to develop a login and signup form.
Scenario
We are building two forms:
Login Form - It will have a username and password field.
Signup Form - It will have a username, email, and password field.
Without the DRY principle, we might duplicate a lot of code between these forms. Instead, we will use reusable components to keep the code clean, maintainable, and efficient.
1. Reusable Input Component
First, let's create a reusable Input
component. We don’t want to repeat the code for text fields (username, email, password), so we’ll abstract it into a reusable component.
import React from "react";
function Input({ type, placeholder, value, onChange }) {
return (
<input
type={type}
placeholder={placeholder}
value={value}
onChange={onChange}
/>
);
}
export default Input;
Explanation:
The
Input
component takes four props:type
: Specifies the type of the input field (e.g., "text", "email", "password").placeholder
: The placeholder text for the input field.value
: The value of the input field (usually coming from React state).onChange
: A function that updates the state when the input changes.
This component is reusable and can be used for any input field across the app.
2. Reusable Button Component
Next, we’ll create a reusable Button
component, which can be customized for different actions (e.g., "Login", "Signup").
import React from "react";
function Button({ text, styleClass, onClick }) {
return (
<button className={styleClass} onClick={onClick}>
{text}
</button>
);
}
export default Button;
Explanation:
The
Button
component is customizable via three props:text
: The text inside the button (e.g., "Login", "Signup").styleClass
: A CSS class for button styling.onClick
: The function to handle what happens when the button is clicked (such as form submission).
3. Reusable Form Component
Here’s where we bring everything together. We create a generic Form
component that renders the required input fields dynamically and handles form submission.
import React from "react";
import Input from "./Input";
import Button from "./Button";
function Form({ fields, onSubmit, buttonText }) {
return (
<form onSubmit={onSubmit}>
{fields.map((field, index) => (
<Input
key={index}
type={field.type}
placeholder={field.placeholder}
value={field.value}
onChange={field.onChange}
/>
))}
<Button text={buttonText} styleClass="btn-primary" />
</form>
);
}
export default Form;
Explanation:
The
Form
component accepts three props:fields
: An array of objects, where each object represents an input field. The object contains properties liketype
,placeholder
,value
, andonChange
.onSubmit
: A function that gets called when the form is submitted.buttonText
: The text inside the button (e.g., "Login" or "Signup").
This component iterates over the fields
array and renders an Input
component for each field. It also renders a Button
for form submission.
4. Login and Signup Forms (Using DRY Components)
Now that we have reusable components, we can create the login and signup forms by passing in the relevant fields and functionality as props.
Login Form
import React, { useState } from "react";
import Form from "./Form";
function LoginForm() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const handleLoginSubmit = (event) => {
event.preventDefault();
console.log("Login:", { username, password });
// Perform login logic here...
};
return (
<Form
fields={[
{
type: "text",
placeholder: "Username",
value: username,
onChange: (e) => setUsername(e.target.value),
},
{
type: "password",
placeholder: "Password",
value: password,
onChange: (e) => setPassword(e.target.value),
},
]}
onSubmit={handleLoginSubmit}
buttonText="Login"
/>
);
}
export default LoginForm;
Signup Form
import React, { useState } from "react";
import Form from "./Form";
function SignupForm() {
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSignupSubmit = (event) => {
event.preventDefault();
console.log("Signup:", { username, email, password });
// Perform signup logic here...
};
return (
<Form
fields={[
{
type: "text",
placeholder: "Username",
value: username,
onChange: (e) => setUsername(e.target.value),
},
{
type: "email",
placeholder: "Email",
value: email,
onChange: (e) => setEmail(e.target.value),
},
{
type: "password",
placeholder: "Password",
value: password,
onChange: (e) => setPassword(e.target.value),
},
]}
onSubmit={handleSignupSubmit}
buttonText="Signup"
/>
);
}
export default SignupForm;
Explanation:
LoginForm and SignupForm are now minimal, clean, and reusable. They pass down the appropriate fields to the
Form
component.We have used React’s
useState
hook to manage the form state.Each form has its own
onSubmit
handler to process the form data upon submission.
Full App Component
You can now use both the LoginForm
and SignupForm
in your application like this:
import React from "react";
import LoginForm from "./LoginForm";
import SignupForm from "./SignupForm";
function App() {
return (
<div className="App">
<h1>Login</h1>
<LoginForm />
<h1>Signup</h1>
<SignupForm />
</div>
);
}
export default App;
The Benefits of Following DRY
By following the DRY principle:
We avoid code duplication. The
Form
,Input
, andButton
components are reused between login and signup forms.If we need to change the design of a button or input field, we only need to modify the
Button
orInput
component.Our code is maintainable. Each form only needs to define its unique fields, while the generic form logic is handled in a single, reusable component.
The forms are scalable. If you need another form (e.g., for password reset), you can use the same
Form
component and pass different fields.
For understanding of the DRY principle and reusable components in action, check out this detailed tutorial on YouTube:
Conclusion
The concepts of reusable components and the DRY principle are critical for building scalable, maintainable, and efficient React applications. By creating modular, reusable components and avoiding redundant code, you can significantly improve the development process, enhance maintainability, and ensure consistency throughout your app.
By applying these practices, you’ll not only write cleaner code but also save time and effort in the long run. React’s component-based architecture naturally encourages this approach, making it easier to follow the DRY principle and create reusable components.
Happy coding!
Subscribe to my newsletter
Read articles from Jack Pritom Soren directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Jack Pritom Soren
Jack Pritom Soren
Software Engineer (SWE) specializing in Frontend development, proficient in JavaScript, React, Next.js, Angular, and a variety of frontend tools. Also skilled in MERN Stack. Committed to crafting clean, efficient code and driving innovation in every project. Passionate about collaborating with dynamic teams to create impactful solutions and continuously advance in the field of frontend development.