Closed Asghwor closed 2 years ago
@Asghwor I have the exact same issue.. here's my temporary work around...
import { AnyMaskedOptions, MaskElement } from "imask";
import { ComponentType,} from "react";
import { IMaskMixin, IMask } from "react-imask";
// these are your MUI props or whatever custom component props you want.
type InputProps = {};
// these seem to be the defaults, you could extend as needed
type IMaskProps = IMaskInputProps<
AnyMaskedOptions,
false,
string,
MaskElement | HTMLTextAreaElement | HTMLInputElement
>;
export const MaskedInput: ComponentType<IMaskProps & InputProps> = IMaskMixin(({ inputRef, ...props }) => (
<Input ref={inputRef} {...props} />
));
@bradbyte I also managed to create a workaround using nested components:
import { TextField, TextFieldProps } from "@mui/material";
import { ComponentProps } from "react";
import { IMaskMixin } from "react-imask";
const InternalMaskTextField = IMaskMixin((props) => (
<TextField {...props as any}/>
))
type MaskProps = ComponentProps<typeof InternalMaskTextField>
export const MaskTextField = (props: TextFieldProps & MaskProps) => {
return <InternalMaskTextField {...props} />
}
I did try your version, but I ended up getting a linting error on the ref
prop.
It would be nice to have something less hacky and not having to use any
, though.
This is a pretty painful setup, and likely pretty common for anyone trying to integrate with react-hook-form
useController
.
I'd love to not have to mangle types while abstracting a MaskedInput
component
@bradbyte I also managed to create a workaround using nested components:
import { TextField, TextFieldProps } from "@mui/material"; import { ComponentProps } from "react"; import { IMaskMixin } from "react-imask"; const InternalMaskTextField = IMaskMixin((props) => ( <TextField {...props as any}/> )) type MaskProps = ComponentProps<typeof InternalMaskTextField> export const MaskTextField = (props: TextFieldProps & MaskProps) => { return <InternalMaskTextField {...props} /> }
I did try your version, but I ended up getting a linting error on the
ref
prop.It would be nice to have something less hacky and not having to use
any
, though.
I get the error in the below. And react-hook-form does not work with this item. Do you have any suggestions?
react_devtools_backend.js:4026 Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of `EditHotel`.
at MaskTextField
This is how i used it:
<MaskTextField
label="Phone"
variant="standard"
error={errors.phone ? true : false}
mask={"+{00}(000)000-0000"}
overwrite
{...register("phone")}
/>
After a lot of tests, i tried using hooks and i end up with this working code:
PS: Input
here is a styled input using stitches
export type MaskedInputProps = {
maskOptions: IMask.AnyMaskedOptions;
} & InputProps;
export const MaskedInput = forwardRef<HTMLInputElement, MaskedInputProps>(
(props, ref) => {
const { maskOptions, ...rest } = props;
const [opts, setOpts] = useState<IMask.AnyMaskedOptions>(maskOptions);
const { ref: inputMaskRef } = useIMask(opts);
useEffect(() => {
setOpts(maskOptions);
}, [maskOptions]);
// tick to make it work with react-hook-forms and useIMask
function handleRefs(instance: HTMLInputElement | null) {
if (ref) {
if (typeof ref === "function") {
ref(instance);
} else {
ref.current = instance;
}
}
if (instance) {
inputMaskRef.current = instance;
}
}
return <Input ref={handleRefs} {...rest} />;
}
);
MaskedInput.displayName = "MaskedInput";
Here was my approach without hooks, though I very much like your approach:
import { ComponentProps, forwardRef } from 'react'
import { FieldValues, UseControllerProps } from 'react-hook-form'
import { IMaskInput } from 'react-imask'
import { styled } from '../stitches-config'
const StyledMaskedInput = styled(IMaskInput, { /** your styles here */})
export type MaskedInputProps = {
mask: ComponentProps<typeof IMaskInput>['mask']
unmask?: 'typed' | boolean
placeholder?: string
} & UseControllerProps<FieldValues, any> &
typeof StyledMaskedInput
const MaskedInput = forwardRef<
HTMLInputElement,
typeof StyledMaskedInput
>((props, ref) => {
// @ts-ignore
return <StyledMaskedInput ref={ref} {...props} />
})
export default MaskedInput
you can do now
const MyInput = IMaskMixin<
IMask.AnyMaskedOptions, // Mask options
false, // Unmask?
string, // value type
HTMLInputElement, // wrapped element type
{ placeholder: string }, // here is your custom props
>(({ placeholder }) => (
<input placeholder={placeholder} />
));
i have an idea to rearrange parameters of the template in v7.0 to do it like:
After a lot of tests, i tried using hooks and i end up with this working code:
PS:
Input
here is a styled input using stitchesexport type MaskedInputProps = { maskOptions: IMask.AnyMaskedOptions; } & InputProps; export const MaskedInput = forwardRef<HTMLInputElement, MaskedInputProps>( (props, ref) => { const { maskOptions, ...rest } = props; const [opts, setOpts] = useState<IMask.AnyMaskedOptions>(maskOptions); const { ref: inputMaskRef } = useIMask(opts); useEffect(() => { setOpts(maskOptions); }, [maskOptions]); // tick to make it work with react-hook-forms and useIMask function handleRefs(instance: HTMLInputElement | null) { if (ref) { if (typeof ref === "function") { ref(instance); } else { ref.current = instance; } } if (instance) { inputMaskRef.current = instance; } } return <Input ref={handleRefs} {...rest} />; } ); MaskedInput.displayName = "MaskedInput";
I founded a fixed on typescript in your solution with casting a mutable ref on inputMaskRef
import IMask from 'imask'
import {
ComponentProps,
forwardRef,
MutableRefObject,
useEffect,
useState,
} from 'react'
import { useIMask } from 'react-imask'
import { Input } from '../Input'
export type MaskedInputProps = {
maskOptions: IMask.AnyMaskedOptions
} & ComponentProps<typeof Input>
export const MaskedInput = forwardRef<HTMLInputElement, MaskedInputProps>(
(props, ref) => {
const { maskOptions, ...rest } = props
const [opts, setOpts] = useState<IMask.AnyMaskedOptions>(maskOptions)
const { ref: IMaskInput } = useIMask(opts)
const inputMaskRef = IMaskInput as MutableRefObject<HTMLInputElement>
useEffect(() => {
setOpts(maskOptions)
}, [maskOptions])
function handleRefs(instance: HTMLInputElement | null) {
if (ref) {
if (typeof ref === 'function') {
ref(instance)
} else {
ref.current = instance
}
}
if (instance) {
inputMaskRef.current = instance
}
}
return <Input ref={handleRefs} {...rest} />
},
)
I have a question, when I used this implementation above when I paste or use a browser history value on the field, the input doesn't recognize the value, only leaving the input. Why this happened @uNmAnNeR ?
I provided some codesandbox for this problem https://codesandbox.io/s/awesome-hooks-5i0r9d?file=/src/styles.css
I solve ts error below code.
export const MaskNumberField: ComponentType<
IMaskInputProps<HTMLInputElement> & NumberInputProps
> = IMaskMixin(({ inputRef, ...props }) => {
return <NumberInput ref={inputRef} {...props} />
})
I'm trying to create a masked component around Material UI's TextField, using IMaskMixin, like so:
However, I get type errors in the component passed to IMaskMixin because there seems to be no way to get its props to include the type of the internal component.
I also get type errors when trying to pass props to the resulting component that belong to the internal component, even though it passes it properly when TypeScript is disabled using
@ts-ignore
:Is there a way to have IMaskMixin and its resulting component accept generic types for props in addition to its defaults?