react-imask
React bindings for IMask: a masked input component, a useIMask hook, and an IMaskMixin HOC for wrapping any existing input.
Repository Health
Technical Analysis
react-imask provides official React bindings for IMask, one of the most widely used input-masking engines in the JavaScript ecosystem. It ships three integration patterns from a single package: a ready-to-use IMaskInput component, a useIMask hook for wiring mask state into custom-rendered UI, and an IMaskMixin higher-order component for adding masking to an already-existing input-like component (a styled-components input, a design-system TextField, etc.) without rewriting it.
Under the hood it delegates all masking logic to the core imask package (pattern, numeric, date, dynamic, and enum masks) and focuses purely on React lifecycle concerns: creating and destroying the underlying mask instance alongside the component, keeping value/unmaskedValue/typedValue in sync with controlled component state, and re-emitting IMask’s accept/complete events as onAccept/onComplete props.
What You Get
- IMaskInput component - a drop-in
<input>replacement that applies a mask and exposesonAccept/onCompletecallbacks plus amaskRefto the underlying IMask instance. - useIMask hook - wires mask state (
value,unmaskedValue,typedValueand their setters) into any custom-rendered input via a ref, for full control over markup. - IMaskMixin HOC - wraps an existing component (a styled-components input, a design-system field, etc.) with masking behavior via an
inputRefprop, without touching its implementation. - Full re-export of the imask core API -
IMaskand all itsMasked*classes are re-exported, so consumers don’t need a second dependency for advanced mask configuration.
Common Use Cases
- Phone number and date inputs - use pattern masks with
IMaskInputto enforce a fixed format as the user types. - Currency and numeric fields - use the
Numbermask withradix/scale/thousandsSeparatoroptions for formatted amount inputs. - Masking a design-system input - wrap an existing styled or custom input component with
IMaskMixininstead of rewriting it. - Custom-rendered masked fields - use
useIMaskwhen the input markup can’t be a plain<input>but still needs mask state and callbacks.
Under The Hood
Architecture
The package lives inside the imaskjs Lerna monorepo alongside sibling framework bindings (angular-imask, vue-imask, svelte-imask, solid-imask), all wrapping the same core imask engine. Internally, src/index.ts re-exports the core IMask API and three integration surfaces: input.ts (the IMaskInput class-style component, itself built by calling IMaskMixin on a plain <input> renderer), mixin.ts (IMaskMixin, a higher-order component implemented as a legacy React.Component subclass that creates/destroys the mask instance in componentDidMount/componentDidUpdate/componentWillUnmount), and hook.ts (useIMask, a functional hook built from useRef/useState/useEffect that re-implements the same creation, sync, and teardown lifecycle in hooks form). All three funnel through the same IMask(el, opts) factory call and mask.on('accept'|'complete', ...) event wiring, so a change to that core factory contract would ripple through the component, hook, and mixin simultaneously.
Tech Stack
Written in TypeScript against the monorepo’s shared tsconfig.json, with prop-types used for runtime prop validation alongside the TypeScript types. The only runtime dependency is the sibling imask workspace package; React itself is a peer dependency accepting anything >=0.14.0. Builds run through tsc --emitDeclarationOnly (for .d.ts output) plus Rollup (@rollup/plugin-babel, -commonjs, -node-resolve, -terser) to produce dual CJS/ESM bundles wired up through a modern exports map, orchestrated across the monorepo via Lerna 8 and npm workspaces.
Code Quality
No dedicated test files exist under packages/react-imask/src — the package defines lint and build scripts but no test script, so it’s excluded from the monorepo’s lerna run test --parallel; only the core imask package has a test/ directory. Error handling is implicit rather than explicit (no try/catch blocks; correctness leans on TypeScript’s type system and PropTypes runtime warnings). Naming is consistent and the code makes deliberate use of fairly advanced generic types (ExtractMaskOpts, UnmaskValue, ReactMaskProps) to keep callback value types (value vs unmaskedValue vs typedValue) accurate for the specific mask options passed in. ESLint (with @typescript-eslint and eslint-plugin-tsdoc) is configured at the monorepo root and run via each package’s lint script.
What Makes It Unique
The wrapper pattern itself (component + hook + HOC around a masking engine) is a common shape for input-masking libraries. What differentiates it is offering three distinct integration surfaces from one package — covering both the legacy class-based extension pattern (IMaskMixin) and a modern hooks API (useIMask) — combined with the core imask engine’s typed-value support, where typedValue can be returned as a native Date or Number rather than a plain string, a feature most comparable masking wrappers don’t expose.