Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to type forwardRef in separate select component?

The technologies I'm using are react, typescript, and styled-components. I'm trying to create the select component in order to make use of it in React Hook Form. At first, it looks like everything is correct, but I'm getting an error from typescript saying:

No overload matches this call. Overload 1 of 2, '(props: Pick<Pick<Pick<DetailedHTMLProps<SelectHTMLAttributes, HTMLSelectElement>, "form" | ... 262 more ... | "onTransitionEndCapture"> & { ...; } & ISelectProps, "form" | ... 264 more ... | "options"> & Partial<...>, "form" | ... 264 more ... | "options"> & { ...; } & { ...; }): ReactElement<...>', gave the following error. Type 'ForwardedRef' is not assignable to type 'RefObject | (RefObject & ((instance: HTMLSelectElement | null) => void)) | (((instance: HTMLSelectElement | null) => void) & RefObject<...>) | (((instance: HTMLSelectElement | null) => void) & ((instance: HTMLSelectElement | null) => void)) | null | undefined'. Type 'MutableRefObject' is not assignable to type 'RefObject | (RefObject & ((instance: HTMLSelectElement | null) => void)) | (((instance: HTMLSelectElement | null) => void) & RefObject<...>) | (((instance: HTMLSelectElement | null) => void) & ((instance: HTMLSelectElement | null) => void)) | null | undefined'. Type 'MutableRefObject' is not assignable to type '((instance: HTMLSelectElement | null) => void) & RefObject'. Type 'MutableRefObject' is not assignable to type '(instance: HTMLSelectElement | null) => void'. Type 'MutableRefObject' provides no match for the signature '(instance: HTMLSelectElement | null): void'.

Here's my code:

import React, { forwardRef } from "react";
import styled from "styled-components";
import arrow from "../assets/svg/select_arrow.svg";

interface ISelectProps {
  options?: { name: string; value: string | number }[];
  name: string;
  ref?:
    | ((instance: HTMLSelectElement | null) => void)
    | React.RefObject<HTMLSelectElement>
    | null
    | undefined;
}

const SelectContainer = styled.div`
  display: flex;
  align-items: center;
  width: 100%;
  position: relative;
`;

const StyledSelect = styled.select<ISelectProps>`
  width: 100%;
  height: 30px;
  padding: 0 10px;
  background: ${({ theme }) => theme.colors.transparentButton};
  color: white;
  border: 1px solid white;
  border-radius: ${({ theme }) => theme.borderRadius};
  font-size: 14px;
  -moz-appearance: none; /* Firefox */
  -webkit-appearance: none; /* Safari and Chrome */
  appearance: none;
  &:focus,
  &:active {
    outline: none;
  }
`;

const StyledArrow = styled.img`
  position: absolute;
  right: 10px;
  padding: inherit;
`;

const Select = forwardRef(({ options, name }: ISelectProps, ref) => {
  return (
    <>
      <SelectContainer>
        <StyledSelect name={name} ref={ref}>
          {options?.map((op) => (
            <option value={op.value}>{op.name}</option>
          ))}
        </StyledSelect>
        <StyledArrow src={arrow} />
      </SelectContainer>
    </>
  );
});

export default Select;

codesandbox.io/s/eager-hertz-opbsi?file=/src/select.tsx

What Am I doing wrong? Thanks in advance for all the helpful answers!

like image 902
zagoor Avatar asked Sep 05 '26 05:09

zagoor


1 Answers

First of all, ref is not a part of props in this case. You should provide two generic parameters for forwardRef to help TS narrow the types.

What is ref? It is just HTML element

import React, { forwardRef } from "react";
import styled from "styled-components";

interface ISelectProps {
  options?: { name: string; value: string | number }[];
  name: string;

}

const SelectContainer = styled.div`
  display: flex;
  align-items: center;
  width: 100%;
  position: relative;
`;

const StyledSelect = styled.select<ISelectProps>`
  width: 100%;
  height: 30px;
  padding: 0 10px;
  background: ${({ theme }) => theme.colors.transparentButton};
  color: white;
  border: 1px solid white;
  border-radius: ${({ theme }) => theme.borderRadius};
  font-size: 14px;
  -moz-appearance: none; /* Firefox */
  -webkit-appearance: none; /* Safari and Chrome */
  appearance: none;
  &:focus,
  &:active {
    outline: none;
  }
`;

const StyledArrow = styled.img`
  position: absolute;
  right: 10px;
  padding: inherit;
`;

const Select = forwardRef<HTMLSelectElement, ISelectProps>(({ options, name }, ref) => {
  return (
    <>
      <SelectContainer>
        <StyledSelect name={name} ref={ref}>
          {options?.map((op) => (
            <option value={op.value}>{op.name}</option>
          ))}
        </StyledSelect>
      </SelectContainer>
    </>
  );
});

export default Select;

Playground link

like image 65
captain-yossarian Avatar answered Sep 07 '26 18:09

captain-yossarian



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!