How to test dynamic react components - reactjs

I'm new to unit testing and I am trying to test validation messages on my login form. When the user enters incorrect information and submits the form, an error message component is displayed. When I try to test if this error component exists I get a falsy response. Any help would be appreciated.
onSubmit = () => {
const errors = this.validate(this.state.data);
this.setState({ errors });
console.log("test");
};
validate = data => {
const errors = {};
if (!Validator.isEmail(data.email)) errors.email = "Invalid Email";
if (!data.password) errors.password = "Password required";
return errors;
};
render() {
const { data, errors } = this.state;
return (
<div>
<Form onSubmit={this.onSubmit}>
<Form.Field>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
name="email"
placeholder="example#example.com"
value={data.email}
onChange={this.onChange}
/>
{errors.email && <InlineError text={errors.email} />}
</Form.Field>
<Form.Field>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
name="password"
value={data.password}
onChange={this.onChange}
/>
</Form.Field>
<Button type="submit" primary>
Login
</Button>
</Form>
</div>
);
}
and my LoginForm.test.js file
describe("user submits form", () => {
it("Returns no errors with correct data", () => {
const data = {
email: "dennis#gmail.com",
password: "12345"
};
wrapper.find("Button").simulate("click");
expect(wrapper.instance().validate(data)).toEqual({});
});
it("Returns error when error exists", () => {
const data = {
email: "",
password: "12345"
};
wrapper.find("Button").simulate("click");
expect(wrapper.instance().validate(data)).toEqual({
email: "Invalid Email"
});
});
it("Displays InlineError when error exists", () => {
const data = {
email: "",
password: "12345"
};
wrapper.find("Button").simulate("click");
expect(wrapper.contains(<InlineError text="Invalid Email" />)).toBe(true);
});
});
The first two tests pass however the third one fails

For one thing, in the first two tests you are testing the validate method only. The click simulation is doing something, but it does not actually affect the test outcome.
The reason third test fails is likely that setState is async. That means you also have to make your expectation async.

Related

React how to pass onSubmit handler through props to the components

I have developed a simple login component for my project. In this I am having two fields (UserName and Password) along with onSubmit handler. I need to pass the onSubmit handler through the props to the components which accepts two params(Username and Password). When I am calling the onsubmit handler I need to call handler with password and username password as params. I have wrote the logic for the same but when I am rendering I am not getting the textbox to fill (userName and Password). Any one can help me to sort out this issue? Thanks in advance. I have wrote down the code below.
function FormDetails(props) {
return (
<div>
<form onSubmitHandler={props.onSubmitHandler}>
<input type="text" id="user-input" name="userName" />
<input type="password" id="password-input" name="password" />
<button type="submit">Submit</button>
</form>
</div>
);
}
function LoginForm() {
const [form, setForm] = useState({
userName: "",
password: "",
});
const onSubmitHandler = (e) => {
e.preventDefault();
console.log("form.userName", form.userName);
setForm({ [e.target.name]: e.target.value });
};
if (form.userName && form.password == null) {
return <FormDetails onSubmitHandler={onSubmitHandler} />;
}
return (
<div>
UserName:{form.userName}
Password:{form.password}
</div>
);
}
export default LoginForm;
update your if condtion and form element to this
<form onSubmit={props.onSubmitHandler}>
if (!form.userName && !form.password) {
// other code
}
the if condition
the if condition should be revesed because you want to show the form if the values are falsy("" empty string, undefined, null ...)
if (!form.userName || !form.password) {
// ^-- ^--
return <FormDetails onSubmitHandler={onSubmitHandler} />;
}
moving values from child to parent
use a ref for the username & password fields
const userName = useRef(null);
const password = useRef(null);
pass the values up with the handleSubmit callback
<div>
<form
onSubmit={(e) => {
e.preventDefault();
console.log({
userName: userName.current.value,
password: password.current.value
});
props.onSubmitHandler({
userName: userName.current.value,
password: password.current.value
});
}}
>
<input ref={userName} type="text" id="user-input" name="userName" />
<input
ref={password}
type="password"
id="password-input"
name="password"
/>
<button type="submit">Submit</button>
</form>
</div>
Final result
import { useState, useRef } from "react";
function FormDetails(props) {
const userName = useRef(null);
const password = useRef(null);
return (
<div>
<form
onSubmit={(e) => {
e.preventDefault();
console.log({
userName: userName.current.value,
password: password.current.value
});
props.onSubmitHandler({
userName: userName.current.value,
password: password.current.value
});
}}
>
<input ref={userName} type="text" id="user-input" name="userName" />
<input
ref={password}
type="password"
id="password-input"
name="password"
/>
<button type="submit">Submit</button>
</form>
</div>
);
}
function LoginForm() {
const [form, setForm] = useState({
userName: "",
password: ""
});
const onSubmitHandler = (val) => {
setForm(val);
};
if (!form.userName || !form.password) {
return <FormDetails onSubmitHandler={onSubmitHandler} />;
}
return (
<div>
UserName:{form.userName}
Password:{form.password}
</div>
);
}
export default LoginForm;

I can't find an example of how to make an HTTP POST request using useEffect hook along with axios

Lately, I've been trying to make an HTTP POST request using the useEFfect hook along with axios, but the code returns this error in the console: Error: Request failed with status code 422. The POST request is always successfully made when I make it from the form onSubmit handler. However, when I try to make it from the useEffect hook, it gives that error. Can anybody help me with what I'm doing wrong? Here's the code:
import React, { useState, useEffect } from "react";
import axios from "axios";
function RandomPost() {
const [input, setInput] = useState({
name: "",
email: "",
companyName: "",
password: "",
});
const handleChange = (event) => {
setInput({ ...input, [event.target.id]: event.target.value });
};
const handleSubmit = (e) => {
e.preventDefault();
};
useEffect(() => {
const { name, email, companyName, password } = input;
axios
.post(`https://01hire.collaq.com/user/createCompany`, input)
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
});
}, [input]);
const { name, email, companyName, password } = input;
return (
<div>
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" id="name" value={name} onChange={handleChange} />
</label>
<label>
Email:
<input type="text" id="email" value={email} onChange={handleChange} />
</label>
<label>
Company Name:
<input
type="text"
id="companyName"
value={companyName}
onChange={handleChange}
/>
</label>
<label>
Password:
<input
type="password"
id="password"
value={password}
onChange={handleChange}
/>
</label>
<button type="submit">Submit</button>
</form>
</div>
);
}
export default RandomPost;

React Hooks, state not updating

I m new to react and js.
import React from "react";
import { useState, useEffect } from "react";
const Forminput = () => {
const [errors, setErrors] = useState({}); //state values
const [values, setValues] = useState({
username: "",
email: "",
}); //state values
const change = (e) => {
const { name, value } = e.target;
setValues({
...values,
[name]: value,
});
};
const submitChange = (e) => {
e.preventDefault();
console.log(values);
setErrors(validation(values));
console.log(errors);
};
const validation= (values) => {
if (!values.username.trim()) {
errors.username = "Username required";
}
if (!values.email) {
errors.email = "Email required";
} else if (!/\S+#\S+\.\S+/.test(values.email)) {
errors.email = "Email address is invalid";
}
return errors;
};
useEffect(() => {}, [errors]);
return (
<div>
<h2>Create Your Form </h2>
<form action="" className="form-inputs" onSubmit={submitChange}>
<div className="username">
<label htmlFor="username" className="username">
Username
</label>
<input
id="username"
type="text"
className="username"
name="username"
onChange={change}
value={values.username}
/>
{errors.username && <p>{errors.username}</p>}
</div>
<div className="email">
<label htmlFor="email" className="email">
email
</label>
<input
id="email"
type="text"
name="email"
className="email"
onChange={change}
value={values.email}
/>
{errors.email && <p>{errors.email}</p>}
</div>
<input type="submit" className="signup" />
</form>
</div>
);
};
setErrors state is not updating.
Two fields username and email. when entered username and not email first for time the errors state is updating.
when entered again right email then press submit it is not updating the error state but updating the values state. i tried using useeffect() but not working.
i new here could you please me.
I want to update the errors state too when i update values state. is this right way or help me
This happens because you're mutating the errors object in validation and setting the same errors object when you call setErrors in submitChange. Since errors is the same object reference, this doesn't trigger a re-render. You'll need to return a new object from validation:
const validation= (values) => {
const result = {};
if (!values.username.trim()) {
result.username = "Username required";
}
if (!values.email) {
result.email = "Email required";
} else if (!/\S+#\S+\.\S+/.test(values.email)) {
result.email = "Email address is invalid";
}
return result;
};

react-hook-form handling server-side errors in handleSubmit

I'm having a hard time figuring out how to handle errors that don't necessarily pertain to a single input field in a react-hook-form.
To put it differently, how do I handle handleSubmit errors?
For example, having the following form:
import to from 'await-to-js'
import axios, { AxiosResponse } from 'axios'
import React from "react"
import { useForm } from "react-hook-form"
type LoginFormData = {
username: string,
password: string,
}
export const Login: React.FC = () => {
const { register, handleSubmit } = useForm<LoginFormData>()
const onSubmit = handleSubmit(async (data) => {
const url = '/auth/local'
const [err, userLoginResult] = await to<AxiosResponse>(axios.post(url, data))
if (userLoginResult) {
alert('Login successful')
}
else if (err) {
alert('Bad username or password')
}
})
return (
<div className="RegisterOrLogIn">
<form onSubmit={onSubmit}>
<div>
<label htmlFor="username">username</label>
<input name="username" id="username" ref={register} />
</div>
<div>
<label htmlFor="password">Password</label>
<input type="password" id="password" name="password" ref={register} />
</div>
<button type="submit"> </button>
</form>
</div>
)
}
Is there a react-hook-form way of informing the user that there's an error with either the username or the password?
as in, other than alert()
Perhaps this is answered elsewhere, but I could not find it.
Clarification
The error received from the server does not pertain to a single field:
{
"statusCode": 400,
"error": "Bad Request",
"message": [
{
"messages": [
{
"id": "Auth.form.error.invalid",
"message": "Identifier or password invalid."
}
]
}
],
"data": [
{
"messages": [
{
"id": "Auth.form.error.invalid",
"message": "Identifier or password invalid."
}
]
}
]
}
In order to display the error from the server to your user, you need to use:
setError to set the error programmatically when the server returns an error response.
errors to get the error state of every fields in your form to display to the user.
type FormInputs = {
username: string;
};
const { setError, formState: { errors } } = useForm<FormInputs>();
In your handleSubmit callback
axios
.post(url, data)
.then((response) => {
alert("Login successful");
})
.catch((e) => {
const errors = e.response.data;
if (errors.username) {
setError('username', {
type: "server",
message: 'Something went wrong with username',
});
}
if (errors.password) {
setError('password', {
type: "server",
message: 'Something went wrong with password',
});
}
});
In your component
<label htmlFor="username">username</label>
<input id="username" {...register("username")} />
<div>{errors.username && errors.username.message}</div>
Live Demo
Inspired by #NearHuscarl's answer, I've done the following hack s.t. changes in either the username or the password inputs will remove the single error.
This hack does not scale well if your error is related to multiple fields in the form, but it worked for the login use case.
onSubmit:
const onSubmit = handleSubmit(async (data) => {
const url = '/auth/local'
const [err, userLoginResult] = await to<AxiosResponse>(axios.post(url, data)) // see await-to-js
if (userLoginResult) {
alert('Login successful')
}
else if (err) {
const formError = { type: "server", message: "Username or Password Incorrect" }
// set same error in both:
setError('password', formError)
setError('username', formError)
}
})
component:
return (
<div className="RegisterOrLogIn">
<form onSubmit={onSubmit}>
<div>
<label htmlFor="username">username</label>
<input name="username" id="username" ref={register} />
</div>
<div>
<label htmlFor="password">Password</label>
<input type="password" id="password" name="password" ref={register} />
</div>
<div>{errors.username && errors.password?.message /*note the cross check*/}</div>
<button type="submit"> </button>
</form>
</div>
)
by setting and rendering the error on both errors.password & errors.username, the error will disappear when the user updates either of those fields.

sweetalert pop up if passwords don't match

I want to make good pop up to my project with sweetalert when the passwords don't match:
const SignUp = ({ signUpStart, userError, resetError }) => {
const [userCredentials, setUserCredentials] = useState({
displayName: "",
email: "",
password: "",
confirmPassword: "",
});
const { displayName, email, password, confirmPassword } = userCredentials;
const handleSubmit = async (event) => {
event.preventDefault();
if (password !== confirmPassword) {
alert(`password don't match`);
return;
}
signUpStart({ displayName, email, password });
};
const handleChange = (event) => {
const { name, value } = event.target;
setUserCredentials({ ...userCredentials, [name]: value });
};
return (
<div className="signup">
<h2 className="title">I do not have a account</h2>
<span>Sign up with your email </span>
<form className="sign-up-form" onSubmit={handleSubmit}>
<Form
type="text"
name="displayName"
value={displayName}
onChange={handleChange}
label="Display Name"
required
></Form>
<Form
type="email"
name="email"
value={email}
onChange={handleChange}
label="Email"
required
></Form>
<Form
type="password"
name="password"
value={password}
onChange={handleChange}
label="Password"
required
></Form>
<Form
type="password"
name="confirmPassword"
value={confirmPassword}
onChange={handleChange}
label="Confirm Password"
required
></Form>
<CustomButton type="submit">SIGN UP</CustomButton>
</form>
{userError && userError.message ? (
<SweetAlert
show={!!userError.message}
type="warning"
title="Something went wrong"
text={userError.message}
onConfirm={() => resetError()}
/>
) : null}
</div>
);
};
EDIT: i add my whole component to the question. i deleted my mapstatetoprops and dispatch.
So i basically want to run once when i hit my submit button.
I want to make the alert in the if statement. But I think I need to refactor little. Can anyone help with this?

Resources