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 (breed vs color) 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.

More Posts

KISS Principle in Software Design photo

KISS Principle in Software Design

Keep It Simple, Stupid: How simplicity leads to better code

Basic principles of OOP photo

Basic principles of OOP

An introduction to the fundamental concepts of Object-Oriented Programming (OOP), including encapsulation, inheritance, polymorphism, and abstraction.

Tailwind CSS Utilities and Best Practices photo

Tailwind CSS Utilities and Best Practices

A practical guide to improving your Tailwind CSS workflow using `clsx`, `tailwind-merge`, and a custom `cn` utility. Learn how to write cleaner.