I have fallen into the too-many-rerenders trap while implementing my own hook. The hook itself looked like this:
function useRegexValidate(value: string, regex: RegExp, errorMessage: string, deps: any[] = []): ValidationResult {
const [result, setResult] = useState<ValidationResult>(ValidationResult.Valid);
useEffect(() => {
if (!regex.test(value)) {
setResult(new ValidationResult(false, errorMessage));
} else {
setResult(ValidationResult.Valid);
}
}, [value, regex, errorMessage, ...deps])
return result;
}
It was kind of tricky to find, but the source of error was passing regular expression as RegExp
. During every render a separate instance of RegExp
object was created, thus causing a re-render, during which another instance was created – and so on.
I solved this problem by passing the regular expression as a string:
function useRegexValidate(value: string, regex: string, errorMessage: string, deps: any[] = []): ValidationResult {
const [result, setResult] = useState<ValidationResult>(ValidationResult.Valid);
useEffect(() => {
let regexObj = new RegExp(regex);
if (!regexObj.test(value)) {
setResult(new ValidationResult(false, errorMessage));
} else {
setResult(ValidationResult.Valid);
}
}, [value, regex, errorMessage, ...deps])
return result;
}
I guess I could also solve it by not passing the regular expression in-place, but instead creating a constant value (since the expression is unlikely to change), e.g.
const regex: RegExp = /.../;
useRegexValidate(..., regex, ...);
This would hinder user-friendliness of my hook though, because one would need to remember to call it in a specific way not to cause hard to track bug. So that’s definitely not an option.
I don’t really like the solution I chose, because the RegExp object needs to be created every time the validation is performed. Is there a way to somehow teach React to compare specific objects’ contents instead of merely their references?
Is there a better way to fix my initial code?
3
Answers
Usually my solution when I have objects that I want to use as deps is to JSON.stringify them. This way you can keep the type safety of RegExp.
Well, of course. This is React, it is a responsibility of the developer to keep track of everything. That is why
useMemo
,useCallback
and the dependency array existYou can use a library like
yup
to help you validateIf you don’t want to use library, for your problem, you might not need an effect
You don’t need effect, you don’t need state, you don’t need custom hook, you don’t even need an utility function. Just validate it in the body of the component
const error = regex.test(value) ? '' : errorMessage
Validating it on each render is probably way more optimized that calling
useEffect
on each render.There is a compiler for react, that I think is not stable yet, but it might help you with the keeping the reference of the regex between the renders
Deconstruct the
RegExp
to thesource
andflags
properties. Use them as the dependencies for theuseEffect
, and then rebuild theRegExp
: