Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passing React Refs To The Hook #1015

Closed
cutterbl opened this issue Mar 18, 2024 · 4 comments
Closed

Passing React Refs To The Hook #1015

cutterbl opened this issue Mar 18, 2024 · 4 comments

Comments

@cutterbl
Copy link

Sometimes you're creating your own general custom components, wrapping IMaskInput. In instances like these, where one is using their own custom as a base, the developer may want to pass refs to the component, rather than having the one's created by the hook.

For instance, say your custom wrapper uses it's own hook that builds out generic onAccept and onComplete methods, as well as separating out input properties from mask options. And, say your component props can include their own ref and inputRef. Then a developer does something like:

import { useIMask } from 'react-imask';
import { useMaskProps } from './useMaskProps';

export function MaskedInput (props) {
  const {inputRef, eventHandlers, inputProps, ...maskOpts} = useMaskProps(props);
  const {
    ref,
    maskRef,
    value,
    setValue,
    unmaskedValue,
    setUnmaskedValue,
    typedValue,
    setTypedValue,
  } = useIMask(maskOpts, eventHandlers);
  
  // The 'ref' here is the 'ref' returned from the `useIMask` hook, but originally came in as the `inputRef` prop to the `MaskedInput`
  // and was passed to `useIMask` as part of the `maskOpts`
  return (
    <input ref={ref} {...inputProps} />
  );
}

Currently the code for the useIMask hook creates these refs for the hook, and doesn't allow you to pass in refs. You could default the options in the hook method signature { ref = React.createRef(), inputRef = React.createRef(), ...otherOpts }. By doing this, you allow the developer more flexibility in how they build their components.

@uNmAnNeR
Copy link
Owner

@cutterbl hi, now it's possible to pass ref prop in a second parameters alongside with callbacks. Does it work for you?

@cutterbl
Copy link
Author

This is a fantastic start. Your change gives the developer a way to access the inner inputRef (ref), but it does not yet allow for access to the maskRef.

Functionally this will work. That said, my understanding is that it is bad practice to use a hook outside of the function itself (meaning using it in the method signature). I may be wrong here, but I think this will throw ESLint errors when using the Rules of Hooks plugin. Currently you have

import IMask, { type InputMask, type InputMaskElement, type FactoryOpts } from 'imask';
import { useEffect, useCallback, useState, useRef, Dispatch } from 'react';
import type { MutableRefObject } from 'react';


export default
function useIMask<
  MaskElement extends InputMaskElement,
  Opts extends FactoryOpts=FactoryOpts,
>(
  opts: Opts,
  { onAccept, onComplete, ref=useRef<MaskElement | null>(null) }: {
    ref?: MutableRefObject<MaskElement | null>,
    onAccept?: (value: InputMask<Opts>['value'], maskRef: InputMask<Opts>, e?: InputEvent) => void;
    onComplete?: (value: InputMask<Opts>['value'], maskRef: InputMask<Opts>, e?: InputEvent) => void;
  } = {}
): {
  ref: MutableRefObject<MaskElement | null>,
  maskRef: MutableRefObject<InputMask<Opts> | null>,
  value: InputMask<Opts>['value'],
  setValue: Dispatch<InputMask<Opts>['value']>,
  unmaskedValue: InputMask<Opts>['unmaskedValue'],
  setUnmaskedValue: Dispatch<InputMask<Opts>['unmaskedValue']>,
  typedValue: InputMask<Opts>['typedValue'],
  setTypedValue: Dispatch<InputMask<Opts>['typedValue']>,
} {
  const maskRef = useRef<InputMask<Opts> | null>(null);
 // ...

And this works. But supposedly this (which also works) would be considered the proper way to do this in React.

import IMask, { type InputMask, type InputMaskElement, type FactoryOpts } from 'imask';
import { useEffect, useCallback, useState, , useRef, createRef, Dispatch } from 'react';
import type { MutableRefObject } from 'react';


export default
function useIMask<
  MaskElement extends InputMaskElement,
  Opts extends FactoryOpts=FactoryOpts,
>(
  opts: Opts,
  { onAccept, onComplete, ref=createRef<MaskElement | null>(null) }: {
    ref?: MutableRefObject<MaskElement | null>,
    onAccept?: (value: InputMask<Opts>['value'], maskRef: InputMask<Opts>, e?: InputEvent) => void;
    onComplete?: (value: InputMask<Opts>['value'], maskRef: InputMask<Opts>, e?: InputEvent) => void;
  } = {}
): {
  ref: MutableRefObject<MaskElement | null>,
  maskRef: MutableRefObject<InputMask<Opts> | null>,
  value: InputMask<Opts>['value'],
  setValue: Dispatch<InputMask<Opts>['value']>,
  unmaskedValue: InputMask<Opts>['unmaskedValue'],
  setUnmaskedValue: Dispatch<InputMask<Opts>['unmaskedValue']>,
  typedValue: InputMask<Opts>['typedValue'],
  setTypedValue: Dispatch<InputMask<Opts>['typedValue']>,
} {
  const maskRef = useRef<InputMask<Opts> | null>(null);
 // ...

@uNmAnNeR
Copy link
Owner

@cutterbl maskRef is managed inside the hook so IMO it should not be possible to pass it from outside. maskRef is a combination of element (ref) and mask. It's already possible to pass external ref and it's also possible to reuse mask just passing the mask instance. So i don't see any benefit to exposing maskRef but it will definitely add complexity. Do you have an example where this makes sense?

You suggest using createRef instead of useRef. I am not an expert in React but it seems like useRef should be used in case of functional components/hooks to memoize the reference between renders which makes sense. Pls correct me if i am wrong.

@cutterbl
Copy link
Author

Absolutely see your point, about useRef vs createRef. Seems to me that's a very valid reason to break the Rule of Hooks. Defaulting ref props is critical, for components such as these, but not extremely common, so there's little info out there on the best way to handle it. Reading over the documentation of the two, I would agree with you that this would seem the stable scenario.

My thoughts on the maskRef were purely for parity with the IMaskInput, where you have ref (docs: 'use ref to get access to internal "masked = ref.current.maskRef"') and inputRef (docs: 'access to nested input'). I personally have never needed access to the maskRef, but since you'd called it out in your docs I figured there must be some valid use case. How you have it, in your new changes, is all I personally would need.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants