what type to give ref in React/TypeScript while using React.forwardRef?

Viewed 1202

I just can't get the typings right. I am forwarding a ref which works correctly if I set ref: any as shown below and ref.current.focus() works.

function EditorFeat(): JSX.Element {
  const ref = useRef<HTMLElement | null>(null);

   // ...
   return (
    ......
    <Editor state={state} dispatch={dispatch} ref={ref} />
     ....
)
}

export default EditorFeat;

interface IEditor {
  state: AppState;
  dispatch: React.Dispatch<Action>;
}

// change ref:any to correct typings!
function Editor({ state, dispatch }: IEditor, ref: any): JSX.Element {
// ...
  return <DraftJsEditor ref={ref} /> // import from 'draft-js'
}

export default React.forwardRef(Editor);

but as soon as I try to get ref type right, I keep getting errors!

if I do function Editor({state, dispatch}: IEditor, ref: HTMLElement): JSX.Element

I get next error:

  Overload 1 of 2, '(props: Readonly<DraftEditorProps>): DraftEditor', gave the following error.
    Type 'HTMLElement' is not assignable to type 'string | ((instance: DraftEditor | null) => void) | RefObject<DraftEditor> | null | undefined'.
      Type 'HTMLElement' is not assignable to type 'string'.
  Overload 2 of 2, '(props: DraftEditorProps, context?: any): DraftEditor', gave the following error.
    Type 'HTMLElement' is not assignable to type 'string | ((instance: DraftEditor | null) => void) | RefObject<DraftEditor> | null | undefined'.
      Type 'HTMLElement' is not assignable to type 'string'

if I try React.RefObject<DraftEditor> I get:

Argument of type '({ state, dispatch }: IEditor, ref: RefObject<DraftEditor>) => Element' is not assignable to parameter of type 'ForwardRefRenderFunction<DraftEditor, IEditor>'.
  Types of parameters 'ref' and 'ref' are incompatible.
    Type '((instance: DraftEditor | null) => void) | MutableRefObject<DraftEditor | null> | null' is not assignable to type 'RefObject<DraftEditor>'.
      Type 'null' is not assignable to type 'RefObject<DraftEditor>'
2 Answers

With functional component this works like a charm, even though a little bit unusual:

function RichEditor(props: RichEditorProps) {
  const [editor, setEditor] = React.useState<Editor | null>(null);

  React.useEffect(() => {
    editor?.focus() // or anywhere you want to use it
  }, [editor])
    

  return (
    <Editor
      ref={editor => setEditor(editor)}
    />
  )
}

If you write React.forwardRef directly at the top of the component and not in the line of the return statement, you only have to type React.forwardRef:

interface IEditor {
  state: AppState;
  dispatch: React.Dispatch<Action>;
}

const Editor = React.forwardRef<HTMLInputElement, IEditor>({ state, dispatch }, ref) {
  // ...
  return <DraftJsEditor ref={ref} /> // import from 'draft-js'
}

export default Editor;

Explanation: You type React.forwardRef with two generic type parameters T and P:

React.forwardRef<T, P>(Editor)
  • T is the type of the HTML element you are eventually referencing via DraftJsEditor, so this might be HTMLDivElement or HTMLInputElement or any other element. The TypeScript error message will usually guide you to the right one when first just pick any wrong one.
  • P are the props of the Editor component.

So in your case it could be React.forwardRef<HTMLInputElement, IEditor>(Editor) as you can see in the big solution above.

Related