DEV Community

Cover image for ReactJS Anti Pattern ~Too abstract props~
Ogasawara Kakeru
Ogasawara Kakeru

Posted on

ReactJS Anti Pattern ~Too abstract props~

・Too abstract props lead to repetitive boilerplate implementations in component callers, resulting in increased code duplication.

Bad example
・This Dialog requires us to create actions prop such as OK/Cancel, receiving an array as actions and a function as onAction.

type Action = { label: string; type: string };

type DialogProps = {
  title: string;
  isOpen: boolean;
  onClose: () => void;
  actions: Action[]; // Everything can put.
  onAction: (type: string) => void; //Everything can be return.
};

export const AnyDialog: React.FC<React.PropsWithChildren<DialogProps>> = ({
  title,
  isOpen,
  onClose,
  actions,
  onAction,
  children,
}) => {
  if (!isOpen) return null;
  return (
    <div className="fixed inset-0 z-50">
      <div className="fixed inset-0 bg-black/50" onClick={onClose} />
      <div className="fixed inset-0 flex items-center justify-center p-4">
        <div role="dialog" aria-modal className="w-full max-w-md rounded-lg bg-white shadow-xl">
          <div className="border-b p-4 text-lg font-semibold">{title}</div>
          <div className="p-4">{children}</div>
          <div className="flex justify-end gap-2 border-t p-3">
            {actions.map((a) => (
              <button
                key={a.type}
                className="rounded-md bg-gray-200 px-3 py-2 text-sm hover:bg-gray-300"
                onClick={() => onAction(a.type)}
              >
                {a.label}
              </button>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
};

// Example code that increases every time you implement.
<AnyDialog
  title="Do you delete?"
  isOpen={open}
  onClose={() => setOpen(false)}
  actions={[
    { label: "Cancel", type: "cancel" },
    { label: "Delete", type: "confirm" },
  ]}
  onAction={(t) => (t === "confirm" ? doDelete() : setOpen(false))}
>
  This operation can not be canceled.
</AnyDialog>;

Enter fullscreen mode Exit fullscreen mode

Improvement Example
When the purpose is fixed, it should be extracted as a distinct purpose-specific component (or a distinct variant). This allows the use of specific types, eliminating duplicate implementations on the calling side.

type ConfirmDialogProps = {
  title: string;
  isOpen: boolean;
  confirmText?: string;
  cancelText?: string;
  destructive?: boolean;
  onConfirm: () => void;
  onCancel: () => void;
};

export const ConfirmDialog: React.FC<React.PropsWithChildren<ConfirmDialogProps>> = ({
  title,
  isOpen,
  confirmText = "OK",
  cancelText = "Cancel",
  destructive,
  onConfirm,
  onCancel,
  children,
}) => {
  if (!isOpen) return null;
  return (
    <div className="fixed inset-0 z-50">
      <div className="fixed inset-0 bg-black/50" onClick={onCancel} />
      <div className="fixed inset-0 flex items-center justify-center p-4">
        <div role="dialog" aria-modal className="w-full max-w-md rounded-lg bg-white shadow-xl" onClick={(e) => e.stopPropagation()}>
          <div className="border-b p-4 text-lg font-semibold">{title}</div>
          <div className="p-4">{children}</div>
          <div className="flex justify-end gap-2 border-t p-3">
            <button className="rounded-md bg-gray-200 px-3 py-2 text-sm hover:bg-gray-300" onClick={onCancel}>
              {cancelText}
            </button>
            <button
              className={["rounded-md px-3 py-2 text-sm text-white", destructive ? "bg-red-600 hover:bg-red-700" : "bg-blue-600 hover:bg-blue-700"].join(" ")}
              onClick={onConfirm}
            >
              {confirmText}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

// Good example
<ConfirmDialog
  title="Delete this item."
  isOpen={open}
  destructive
  onConfirm={doDelete}
  onCancel={() => setOpen(false)}
>
   This operation can not be canceled.
</ConfirmDialog>;
Enter fullscreen mode Exit fullscreen mode

The principle is to perform abstraction “only to the minimum necessary extent.” When the use case is limited, actively concretize props to improve readability and consistency.

Top comments (0)