简体   繁体   中英

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!

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

You never typed the ref object, check examples in React TypeScript Cheatsheet - forwardRef .

// T typing the ref
forwardRef<T, P = {}>(...)

// Full typing signature
function forwardRef<T, P = {}>(render: ForwardRefRenderFunction<T, P>): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;

// BAD - ref object untyped
const Select = forwardRef(({ options, name }: ISelectProps, ref) => {...}

// GOOD - ref object typed
const Select = forwardRef<HTMLSelectElement, ISelectProps>(
  ({ options, name }, ref) => {...}
);

https://codesandbox.io/s/hungry-architecture-7gsnp?file=/src/select.tsx:665-1038

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM