Photo by Jezer Josué Mejía Otero
DRY Principle in Software Design
DRY stands for “Don’t Repeat Yourself.” It’s a software design principle that encourages reducing duplication in logic, structure, or behavior. Every piece of knowledge should live in a single, unambiguous place in the system.
This doesn’t just apply to code blocks, it includes logic, configuration, business rules, even naming conventions. Repetition often leads to inconsistencies, bugs, and harder maintenance.
Bad Example Breaking DRY
import React, { useState } from "react";
const PuppyForm = () => {
const [name, setName] = useState("");
const [breed, setBreed] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
// Submit logic for puppy
alert(`Puppy submitted: ${name}, breed: ${breed}`);
};
return (
<form onSubmit={handleSubmit}>
<h2>Add Puppy</h2>
<input
type="text"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="text"
placeholder="Breed"
value={breed}
onChange={(e) => setBreed(e.target.value)}
/>
<button type="submit">Submit Puppy</button>
</form>
);
};
const KittenForm = () => {
const [name, setName] = useState("");
const [color, setColor] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
// Submit logic for kitten
alert(`Kitten submitted: ${name}, color: ${color}`);
};
return (
<form onSubmit={handleSubmit}>
<h2>Add Kitten</h2>
<input
type="text"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="text"
placeholder="Color"
value={color}
onChange={(e) => setColor(e.target.value)}
/>
<button type="submit">Submit Kitten</button>
</form>
);
};
export { PuppyForm, KittenForm };
Why This Breaks DRY
- Duplicate form logic and state management
- Only differences are field names (
breedvscolor) and labels - Difficult to maintain, test, or extend shared behavior
- UI or validation changes require updates in multiple places
Good Example: Reusable Generic Form Component
import React, { useState } from "react";
// Generic form component that receives configuration for each animal
const AnimalForm = ({ type, fields, onSubmit }) => {
// Initialize form state based on fields
const initialState = fields.reduce((acc, field) => {
acc[field.name] = "";
return acc;
}, {});
const [formData, setFormData] = useState(initialState);
const handleChange = (e) => {
setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));
};
const handleSubmit = (e) => {
e.preventDefault();
onSubmit(formData);
};
return (
<form onSubmit={handleSubmit}>
<h2>Add {type}</h2>
{fields.map(({ name, placeholder }) => (
<input
key={name}
name={name}
type="text"
placeholder={placeholder}
value={formData[name]}
onChange={handleChange}
/>
))}
<button type="submit">Submit {type}</button>
</form>
);
};
// Specific usage for Puppy and Kitten forms
const PuppyForm = () => (
<AnimalForm
type="Puppy"
fields={[
{ name: "name", placeholder: "Name" },
{ name: "breed", placeholder: "Breed" },
]}
onSubmit={(data) =>
alert(`Puppy submitted: ${data.name}, breed: ${data.breed}`)
}
/>
);
const KittenForm = () => (
<AnimalForm
type="Kitten"
fields={[
{ name: "name", placeholder: "Name" },
{ name: "color", placeholder: "Color" },
]}
onSubmit={(data) =>
alert(`Kitten submitted: ${data.name}, color: ${data.color}`)
}
/>
);
export { PuppyForm, KittenForm };
Why This Follows DRY
- One reusable component manages state, handlers, and markup
- Differences handled via configuration props (
type,fields) - Easy to add new forms without duplicating code
- Changes to logic or UI happen once, applied everywhere
- Results in cleaner, maintainable, and less error-prone code
Embracing the DRY principle helps us write cleaner, more efficient code. By avoiding duplication, we make our applications easier to maintain and scale. Investing time upfront in creating reusable components pays off in the long run, saving effort and reducing errors as the project grows. Keep DRY in mind, it’s a simple yet powerful mindset that leads to better software design.