One issue that I'm seeing is that in the Input
component, you're using props.input
, why?
const Input = forwardRef((props, ref) => {
return <input ref={ref} {...props}></input>;
});
You want exactly the props that you're sending to be assigned to the component.
Next up, you're doing value.includes('@')
, but are you sure that value
is not undefined
?
const { inputRef, isValid } = useInput(value =>
value && value.includes('@')
);
This would eliminate the possibility of that error.
Solving the issue with the inputRef is undefined
is not hard to fix.
Afterward, you're going to face another issue. The fact that you're using useRef
(uncontrolled) will not cause a rerender, such that, if you update the input content, the isValid
won't update its value.
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. (React Docs)
This is a personal note, but I find uncontrolled components in general hard to maintain/scale/..., and also refs are not usually meant to do this kind of stuff. (yes, yes you have react-form-hook
which provides a way of creating forms with uncontrolled components, and yes, it's performant).
In the meantime, while I'm looking into this a little more, I can provide you a solution using useState
.
const useInput = (validationRule, initialValue='') => {
const [value, setValue] = useState(initialValue)
const onChange = (e) => setValue(e.target.value)
const isValid = validationRule && validationRule(value)
return {
inputProps: {
value,
onChange
},
isValid
}
}
So, right here we're having a function that has 2 parameters, validationRule
and initialValue
(which is optional and will default to text if nothing is provided).
We're doing the basic value / onChange
stuff, and then we're returning those 2 as inputProps
. Besides, we're just calling the validationRule
(beforehand, we check that it exists and it's sent as parameter).
How to use:
export default function SomeForm() {
const { inputProps, isValid } = useInput((value) => value.includes('@'));
return <Input {...inputProps}/>;
}
The following part is something that I strongly discourage.
This is bad but currently, the only way of seeing it implemented with refs is using an useReducer
that would force an update onChange
.
Eg:
const useInput = (validationRule) => {
const [, forceUpdate] = useReducer((p) => !p, true);
const inputRef = useRef();
const onChange = () => forceUpdate();
const isValid = validationRule && validationRule(inputRef.current?.value);
return {
inputRef,
isValid,
onChange
};
};
Then, used as:
export default function SomeForm() {
const { inputRef, onChange, isValid } = useInput((value) =>
value && value.includes("@")
);
console.log(isValid);
return <Input ref={inputRef} onChange={onChange} />;
}