I am trying to test button enabling and disabling in my React component. The problem is that the button is never enabled and it's never enabled because of my calls to validateEmail()
and validateForm()
inside handleInputChange()
. I tried removing those calls and just setting isValid
to true and it worked.
Functions inside my Contact functional component:
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
email: '',
message: '',
})
const [isValid, setIsValid] = useState(false)
function validateForm(obj) {
return Object.values(obj).every((v) => v.length > 0)
}
const validateEmail = (email) => {
let re = /\S+@\S+\.\S+/
return re.test(email)
}
const handleInputChange = (e) => {
const { name, value } = e.target
setFormData({
...formData,
[name]: value,
})
if (validateEmail(formData.email) && validateForm(formData)) {
setIsValid(true)
}
}
And then I have a couple of inputs and a button:
<InputField
type="email"
name="email"
id="email"
autocomplete="email"
onChange={(e) => handleInputChange(e)}
label="Email"
value={formData.email}
testId="email"
/>
<Button
type="submit"
text="Send message"
disabled={!isValid}
testId="submit"
/>
My test:
test('send button is enabled when fields are not empty', async () => {
render(<Contact />);
let firstnameVal = "Test First Name";
let lastnameVal = "Test Last Name";
let email = "email@email.com";
let message = "This is a message";
fireEvent.change(screen.getByTestId("first-name"), {target: {value: firstnameVal}});
fireEvent.change(screen.getByTestId("last-name"), {target: {value: lastnameVal}});
fireEvent.change(screen.getByTestId("email"), {target: {value: email}});
fireEvent.change(screen.getByTestId("message"), {target: {value: message}});
expect(screen.getByTestId('submit')).toBeEnabled();
});
How can I fix this? Am I missing something?
In React, state updates via the setState
(or setFormData
in your case) function are asynchronous. This means that when you call setFormData
, the formData
state does not immediately update to the new value. Instead, the update is scheduled, and the component will re-render with the new state at a later time. If you want to use the actual value immediately, you can use the value from the event instead of the state(formData in your case). Try this approach.
const handleInputChange = (e) => {
const { name, value } = e.target;
const newValues = {
...formData,
[name]: value,
};
setFormData(newValues);
if (validateEmail(newValues.email) && validateForm(newValues)) {
setIsValid(true);
}
};
Further I have a doubt why you using testId
instead of data-testid