DEV Community

Cover image for Let's DRY button "disabled"
Dominik Dosoudil
Dominik Dosoudil

Posted on

Let's DRY button "disabled"

I got this idea while fixing a bug where a button wasn’t disabled when it was supposed to be. I usually add a condition inside the click handler to make sure it doesn’t run (even though disabled should prevent that—just to be safe). Sometimes this is even necessary to avoid type errors.

What bothers me is that the condition for the handler’s early return and the condition for disabling the button are usually the same. Not only is that annoying, but it’s also exactly where bugs tend to happen: something gets added to the condition in one place but not the other. That’s where the developer experience really suffers.

const Example = () => {
  const [value, setValue] = useState('');

  const handleSubmit = () => {
    if (value === '') {
      return; // early return, value empty
    }

    // do something with the value
  };

  return (
    <div>
      <input value={value} onChange={(e) => setValue(e.target.value)} />

      <button type="submit" disabled={value === ''} onClick={handleSubmit}>
        Submit
      </button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

So I had this idea: what if disabled is redundant? A button is effectively enabled only when there’s a handler that actually does something. Otherwise, the user can click it and nothing happens—which isn’t great UX.

The trick is simple: imagine a button that enables or disables itself based on whether a handler exists.

const Btn = ({ onClick = null }: { onClick?: null | (e) => void }) => {
  return <button disabled={!onClick} onClick={onClick} />
}
Enter fullscreen mode Exit fullscreen mode

and then:

const Example = () => {
  const [value, setValue] = useState('');

  const handleSubmit =
    value === ''
      ? null
      : () => {
          // do something with the value
        };

  return (
    <div>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      <button onClick={handleSubmit} />
    </div>
  );
};

Enter fullscreen mode Exit fullscreen mode

This way, you’ll never forget to disable the button, and the enabled state is directly tied to the handler. That makes it clear exactly when the handler can be invoked.

Top comments (0)