TS2769

Fix TS2769: React Hook Form register with custom inputs

TS2769 hits when you spread register() onto a custom input that doesn't forward refs or accept the right props. The fix is a proper forwardRef component.

You've got a custom <Input /> or <TextField /> component, you drop {...register('email')} on it, and TypeScript screams TS2769 at you. No overload matches this call. The overload list is useless — forty lines of JSX.IntrinsicElements signatures that don't apply. This shows up almost immediately after you switch from a raw <input> to your own wrapper, or when you paste a snippet from the RHF docs into a styled component.

It's rarely about your form types. It's about what your component actually accepts as props.

What's actually going wrong

register('email') returns a UseFormRegisterReturn object. That object has four keys: name, onChange, onBlur, and ref. When you spread it onto a native <input>, TypeScript is happy because HTMLInputElement accepts all of those.

Your custom component almost certainly doesn't. Maybe it's typed as { label: string; placeholder?: string }. Maybe it extends React.ComponentPropsWithoutRef<'input'> but drops ref. Either way, TypeScript sees a ref key on the spread object and no matching prop on your component, and it falls through every overload trying to find one that fits. Nothing fits. TS2769.

The other common trigger: you're on React 18 and your component is a function that returns JSX but never touches forwardRef. Plain function components don't accept ref as a prop. Period. React itself will warn at runtime, and TypeScript catches it first.

The fix

  1. Wrap the component in forwardRef and type the ref as the underlying DOM element. If you're wrapping a native input, that's HTMLInputElement. If you're wrapping a library component like MUI's TextField, it's HTMLInputElement too, but you'll need to forward the ref through to inputRef because TextField uses that prop name instead of ref.

    import { forwardRef, InputHTMLAttributes } from 'react';
    
    interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
      label?: string;
    }
    
    export const Input = forwardRef<HTMLInputElement, InputProps>>(
      ({ label, ...rest }, ref) => (
        <label>
          {label}
          <input ref={ref} {...rest} />
        </label>
      )
    );
    Input.displayName = 'Input';

    Note the props type. Extending InputHTMLAttributes<HTMLInputElement> is what pulls in name, onChange, onBlur, and everything else RHF's return object contains. Skip that and you'll get a different error — usually TS2322 on the spread.

  2. If you're on React 19, drop forwardRef. Ref is a regular prop now. This is the one case where the modern React docs are actually cleaner than the old pattern:

    interface InputProps extends React.ComponentProps<'input'> {
      label?: string;
    }
    
    export function Input({ label, ref, ...rest }: InputProps) {
      return (
        <label>
          {label}
          <input ref={ref} {...rest} />
        </label>
      );
    }

    Mixed React versions in a monorepo is a recipe for confusion here. Check your root package.json before you pick one.

  3. Make sure the component's prop type doesn't narrow onChange. I've seen people write onChange?: (value: string) => void on their custom input. That's not the DOM signature. RHF passes a ChangeEventHandler — it hands you an event, not a value. If your component expects a value, TS2769 comes right back because the spread's onChange doesn't match. Use ChangeEventHandler<HTMLInputElement>, or strip onChange out and call it manually, but don't try to make RHF speak your custom API.

  4. For MUI, Chakra, or any wrapper that renames the ref, forward it correctly. MUI's TextField takes inputRef, not ref. If you're writing a wrapper around it, the pattern is:

    export const WrappedField = forwardRef<HTMLInputElement, Props>(
      (props, ref) => <TextField {...props} inputRef={ref} />
    );

    Forgetting inputRef here means RHF never gets a handle on the actual input, and your formState.errors won't clear on focus. That's a runtime bug, not a TypeScript one, but it starts with the same confusion.

When it still fails

  • Check your tsconfig strictness. If strictFunctionTypes is off, some of these slips through silently in dev and blows up in CI. Turn it on and fix it once.
  • Look at the actual overload error, not the first one. TypeScript prints candidates in order. Scroll to the last few — they're usually the JSX-intrinsic ones and they tell you which prop didn't match.
  • Don't cast with as any. It hides the problem and you'll hit a runtime ref is not a function when RHF tries to focus the field on submit. Fix the component.
  • Verify you're not passing register() to a wrapper that renders conditionally. If your component returns null when disabled is set, RHF's ref callback never fires. That's a separate issue but it masquerades as a typing problem.
  • Duplicate @types/react versions. Run npm ls @types/react. Two majors in the tree will make JSX.IntrinsicElements resolve to two different types and TS2769 is a classic symptom. Dedupe with your package manager's override field.

The real fix is almost always step one: the component needs to actually accept the props register() returns, and it needs to be forwardRef (or React 19). Don't fight the types. Match them.

Related Errors in Programming & Dev Tools
0XC00D10A8 Fix NS_E_WMPCORE_PLAYLIST_STACK_EMPTY (0XC00D10A8) in WMP 0XC0000374 STATUS_HEAP_CORRUPTION 0XC0000374: Direct Fixes That Actually Work java.lang.OutOfMemoryError: Java heap space Fix Java OutOfMemoryError: Java heap space 0XC0000025 STATUS_NONCONTINUABLE_EXCEPTION 0xC0000025 on Windows

Was this solution helpful?

EP
Erropedia Team
Tech Support Editors
The Erropedia editorial team researches and documents real-world tech errors from across Windows, Linux, macOS, networking, databases, cloud platforms, and more. Every solution is reviewed for accuracy and updated as software and systems evolve.