0
votes

I have an example I have created in codepen. The table renders with th, but within my IDE the typescript complains about the type at this line:

<TH handleSort={handleSort} ref={refsTh[ind]}>

--- Error Type 'MutableRefObject' is not assignable to type '((instance: HTMLTableHeaderCellElement | null) => void) | RefObject | null | undefined'. Type 'MutableRefObject' is not assignable to type 'RefObject'. Types of property 'current' are incompatible. Type 'unknown' is not assignable to type 'HTMLTableHeaderCellElement | null'. Type 'unknown' is not assignable to type 'HTMLTableHeaderCellElement'.

I have made this example work when not passing an array and instead a single ref. The problem seems to be because I am creating useRefs dynamically inside an array. Can anyone please advise? Thanks

example code: https://codepen.io/inspiraller/pen/OJMRZWQ

// import React from 'react';

// ######################################
type shapeTH = {
  handleSort: () => void,
  children: React.ReactNode // BUG: can't pass children any other way to forwardRef
};

type Ref = HTMLTableHeaderCellElement | null;

const TH = React.forwardRef<Ref, shapeTH>(
  ({children, handleSort}, ref) => (
    <th ref={ref} onClick={handleSort}>
      <span className="thSpan">{children}</span>
    </th>
  )
);

// const TH:React.FC<shapeTH> = ({children, handleSort}) => (
//   <th onClick={handleSort}>
//     <span className="thSpan">{children}</span>
//   </th>
// );

// ##################################################

const App:React.FC = () => {
  const arrTh=['id', 'name', 'colour'];

  type shapeRef = ReturnType<typeof React.useRef>;

  const refsTh: Array<shapeRef> = arrTh.map(() => (
    React.useRef<HTMLTableHeaderCellElement>(null)
  ));
  const handleSort: shapeTH['handleSort'] = () => {};
  return (
    <table className="tableGeneric">
      <thead>
        <tr>
          {
            arrTh.map((item, ind) => (
              <TH handleSort={handleSort} ref={refsTh[ind]}>
                {item}
              </TH>
            ))
          }
        </tr>
      </thead>
    </table>
  );
};

// export default App;

ReactDOM.render(
  <App/>,
  document.getElementById('app')
);


1

1 Answers

0
votes

useRef is a generic, which you know because you wrote React.useRef<HTMLParagraphElement>(null) correctly. The type of the variable refOne would have been inferred as React.RefObject<HTMLParagraphElement>.

The problem is that you manually assigned a type to refOne which is more vague than that. When you look at the return type of the useRef function you don't know what generic type it will be called with, so type shapeRef = ReturnType<typeof React.useRef> is React.MutableRefObject<unknown> where unknown is the type of ref.current.

You don't need to manually assign types to variables when their types are known from their value. This is fine:

const App: React.FC = () => {
  const refOne = React.useRef<HTMLParagraphElement>(null);
  const handleSort = () => { };
  return (<P handleSort={handleSort} ref={refOne}>hello</P>);
};

Typescript Playground Link

For your array example you have the same issue.

const refsTh = arrTh.map(() => (
  React.useRef<Ref>(null)
));

Typescript Playground Link

FYI #1: You don't want to call a hook on an array may if the array can change length, but your App has a fixed array so it's fine.

FYI #2: You can use React.PropsWithChildren<P> to add the children prop. But setting it as regular prop works too.