1
votes

live demo link: https://codesandbox.io/s/style-components-spread-error-cg189?file=/src/MySlider.tsx

I try to wrap input element into react component to apply custom styles and markup. I would like to use it just like any other input element, by using value, onChange etc. But typescript does not like spreading props on styled input. I could cast props to any, but I would like to know why this is a problem in the first place. Thanks!

const MyRange = styled.input.attrs({ type: 'range' as string })`
  /* styles */
`;

export const MySlider: FC<HTMLProps<HTMLInputElement>> = (props) => {
  return (
    <>
      <MyRange {...props} />
    // ^^^^^^^ Types of property 'ref' are incompatible.
      <span>{props.value}</span>
    </>
  );
};

1

1 Answers

0
votes

The HTMLProps<HTMLInputElement> generic type is including types that can't be assigned to a styled-component. If it were a plain HTML input element, then those types would be valid. Instead, I'd recommend type casting your MySlider props.

Working demo:

Edit Styled Component - Input Range Typing


App.tsx

import * as React from "react";
import MySlider from "./MySlider";
import "./styles.css";

export default function App() {
  const [value, setValue] = React.useState("10");

  function onChange(e: React.ChangeEvent<HTMLInputElement>): void {
    setValue(e.target.value);
  }

  return (
    <div className="App">
      <MySlider value={value} onChange={onChange} step="10" />
    </div>
  );
}

MySlider.tsx

import * as React from "react";
import styled from "styled-components";

const MyRange = styled.input.attrs({ type: "range" })`
  appearance: none;
  background: gray;
  width: 100%;
  height: 40px;
  outline: none;
  box-sizing: border-box;
  margin: 0;

  ::-webkit-slider-thumb {
    appearance: none;
    height: 40px;
    width: 40px;
    background: red;
    cursor: pointer;
  }
`;

export type MySliderProps = {
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  value: string;
  step: string;
};

export const MySlider = (props: MySliderProps): React.ReactElement => (
  <>
    <MyRange {...props} />
    <span>{props.value}</span>
  </>
);

export default MySlider;

Be careful about mixing numbers and strings. While you initially set your value state as a number, it's being set a string in the onChange callback. For example, event.target.value is a string (numbers are stored as strings within the DOM). Either keep it as a string or utilize parseInt to retain value as a number:

function onChange(e: React.ChangeEvent<HTMLInputElement>): void {
  setValue(parseInt(e.target.value, 10));
}

On a separate note, there's a push to deprecate the usage of FC.