Property dataset does not exist react typescript error

When and why we get this type error?

If you use input or any HTML tag with HTML data attributes while working with react with typescript, you will undoubtedly encounter this error in your event handler functions.

This is one of those cases where typescript is helpless, and we must assist the type system by providing some additional information. This short post will go over how to handle this properly.

Let's start by creating a custom dataset generic type that extends the native dataset type.

export type DatasetInjector<T, D extends DOMStringMap> = T & {
  dataset: D;
};

How does it work?

In DatasetInjector, the generic T specifies the HTML typescript type, such as HTMLInputElement or HTMLTextAreaElement. The second generic D is used to provide missing information about data attributes of input tag (or any other HTML tag).

Example

Here we are adding the data attribute inputId for HTMLInputElement

// ChangeEventHandler<DatasetInjector<HTMLInputElement, { inputId: string }>>

export type DatasetInjector<HTMLInputElement, D extends DOMStringMap> = HTMLInputElement & {
  dataset: D; // D -> { inputId: string }
};

Usage in real world

Following example uses a simple Inputs component to demonstrate the custom generic helper in action. I have two inputs with a custom attribute inputId. This is used to determine which input was called the onChange handler when the password or confirm password was entered.

import React, { ChangeEventHandler, useState } from 'react';

const Inputs = () => {
  const [value, setValue] = useState({
    password: '',
    confirmPassword: '',
  });

  const changeHandler: ChangeEventHandler<
    DatasetInjector<HTMLInputElement, { inputId: string }>
  > = (e) => {
    setValue((prevState) => ({
      ...prevState,
      [e.target.dataset.inputId]: e.target.value,
    }));
  };

  const textareaChangeHandler: ChangeEventHandler<
    DatasetInjector<HTMLTextAreaElement, { inputId: string }>
    > = (e) => {
    setValue((prevState) => ({
      ...prevState,
      [e.target.dataset.inputId]: e.target.value,
    }));
  };

  return (
    <>
      <input
        data-input-id='password'
        onChange={changeHandler}
        value={value.password}
      />
      <textarea
        data-input-id='confirmPassword'
        onChange={textareaChangeHandler}
        value={value.confirmPassword}
      />
    </>
  );
};

With this custom generic type in place, the type system will be pleased because it will receive all the missing information required to provide type safety.

That's all there is to this code-block; Go ahead and give it a shot.

If you enjoyed the article, please spread the word and keep an eye out for future updates! See you in the next article 😊