React how to pass onSubmit handler through props to the components - reactjs

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;

Related

Test passing for button disabled when it should not (Formik | react-testing library)

I am using a formik form to sign up and testing it with react testing library. The submit button is disabled if the email or password fields are invalid. I am testing if my btn is enabled or disabled when i type certain input. I am performing 2 tests the first one with invalid inputs and the second with valid inputs but the first one is always passing even if it shouldn't. The 2nd test is behaving as it should
const MockedSignUpFormView=()=>{
return(
<BrowserRouter>
<SignUpFormView/>
</BrowserRouter>
)
}
it('btn is disabled if sign up with wrong credentials ',async () => {
render(<MockedSignUpFormView />);
const emailInput = screen.getByRole('textbox', { name: /email address/i});
const passInput = screen.getByPlaceholderText(/password/i);
const confirmPassInput = screen.getByLabelText(/confirm password/i);
const signUpBtn = screen.getByRole('button', { name: /sign up/i})
userEvent.type(emailInput,'mnbmbm#gmail.com')
userEvent.type(passInput,'sadasdas')
userEvent.type(confirmPassInput,'czxcxzcxz')
await waitFor(async ()=>{
expect(signUpBtn).not.toHaveAttribute('disabled')})
screen.debug()
});
it('btn is enabled if sign up with right credentials ',async () => {
render(<MockedSignUpFormView />);
const emailInput = screen.getByRole('textbox', { name: /email address/i});
const passInput = screen.getByPlaceholderText(/password/i);
const confirmPassInput = screen.getByLabelText(/confirm password/i);
const signUpBtn = screen.getByRole('button', { name: /sign up/i})
userEvent.type(emailInput,'mnbmbm#gmail.com')
userEvent.type(passInput,'czxcxzcxz')
userEvent.type(confirmPassInput,'czxcxzcxz')
await waitFor(async ()=>{
expect(signUpBtn).toBeEnabled()})
screen.debug()
});
I am using screen.debug and i am getting disabled on the first btn but still test keeps passing for this expect(signUpBtn).not.toHaveAttribute('disabled')}) and this expect(signUpBtn).not.toBeDisabled()
Sreen.debug() for first btn <button class="login__btn" disabled="" type="submit">
Sreen.debug() for 2nd btn <button class="login__btn" type="submit">
SignUpFormView component
const SignUpFormView=(props:any)=>{
const [error,setError]=useState(false);
const navigate=useNavigate ();
return(
<React.Fragment>
<Formik
initialValues={{
email: "",
password:"",
confirmPassword:""
}}
validationSchema={Yup.object({
password: Yup.string()
.min(6, "Must be 6 characters or more")
.required("Required"),
confirmPassword: Yup.string()
.min(6, "Must be 6 characters or more")
.required("Required")
.oneOf([Yup.ref('password'), null], 'Passwords must match'),
email: Yup.string()
.email("Invalid email address`")
.required("Required"),
})}
onSubmit={(values, { setSubmitting }) => {
props.onSubmitHandler(values.email,values.password).then((userCredential:any) => {
setSubmitting(false);
navigate('/home');
}).catch((err:any)=>{
setError(err.message);
})
}}>
{formik => {
return (
<nav className={classes["nav"]}>
<h2 className={classes['nav__detail']}>Sign Up</h2>
<Form className={classes['form__login']}>
<Input
label="Email Address"
name="email"
type="email"
/>
<Input
label="Password"
name="password"
type="password"
placeholder='Password'
/>
<Input
label="Confirm Password"
name="confirmPassword"
type="password"
/>
{(formik.isSubmitting || error) /*&& renderResponseItem(formik.isSubmitting,error)*/}
{!formik.isSubmitting && <button type="submit" disabled={!formik.isValid || formik.isSubmitting ? true :false} className={classes['login__btn']}>Sign Up</button>}
</Form>
<Link to='/'>Have an account? Login</Link>
</nav>
)
}
}
</Formik>
</React.Fragment>
)
}
Input component
import React from 'react';
import { useField } from "formik";
import classes from './InputView.module.css';
interface Props{
name:string,
id?:string,
placeholder?:string,
label:string,
type:string
}
const Input:React.FC<Props>=(props)=>{
const [field, meta] = useField(props);
const inputClasses=meta.touched && meta.error ? `${classes.input} ${classes.error}`:`${classes.input}`;
return (
<React.Fragment>
<label className={classes.label} htmlFor={props.id || props.name}>{props.label}</label>
<input className={inputClasses} {...field} {...props} id={props.name} placeholder={props?.placeholder}/>
</React.Fragment>
);
}

Login is not a function after logging out then trying to log back in

I am building a react project where I want to implement a log-in/ log out screen.
I am able to succesfully log in but once I log out, I cannot log back in with the error "TypeError
Login is not a function" being displayed. It seems to be coming from LogInForm.js, from the Login(details); line. I cannot spot my mistake here so I would greatly appreciate any feedback. Cheers.
LogInForm.js
import React, { useState } from "react";
function LogInForm({ Login, error }) {
const [details, setDetails] = useState({ email: "", password: "" });
const submitHandler = (e) => {
e.preventDefault();
Login(details);
};
return (
<form onSubmit={submitHandler}>
<div className="form-inner">
<h2>Login</h2>
{/* ERROR!*/}
<div className="form-group">
<label htmlFor="email">Email:</label>
<input
type="email"
name="email"
id="email"
onChange={(e) => setDetails({ ...details, email: e.target.value })}
value={details.email}
/>
</div>
<div className="form-group">
<label htmlFor="password">Password:</label>
<input
type="password"
name="password"
id="password"
onChange={(e) =>
setDetails({ ...details, password: e.target.value })
}
value={details.password}
/>
</div>
<input type="submit" value="Log In" />
</div>
</form>
);
}
export default LogInForm;
LogIn.js
import React, { useState } from "react";
import App from "./App";
import LogInForm from "./LogInForm";
function LogIn() {
const adminUser = {
email: "admin#admin.com",
password: "test123"
};
const [user, setUser] = useState({ name: "", email: "" });
const [error, setError] = useState("");
const Login = (details) => {
if (
details.email == adminUser.email &&
details.password == adminUser.password
) {
console.log("Logged in!");
setUser({ email: details.email, password: details.password });
} else {
console.log("details dont match");
}
};
const Logout = () => {
console.log("Logout");
};
return (
<div className="Display">
{user.email != "" ? (
<App email={user.email} password={user.password} />
) : (
<LogInForm Login={Login} error={error} />
)}
</div>
);
}
export default LogIn;
index.js
import React from "react";
import ReactDOM from "react-dom";
import LogIn from "./LogIn";
ReactDOM.render(<LogIn />, document.getElementById("root"));
App.js
import React from "react";
import LogIn from "./LogIn";
import LogInForm from "./LogInForm";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
email: this.props.email,
password: this.props.password,
};
this.logOut = this.logOut.bind(this);
}
logOut() {
console.log("Logged out");
this.setState({ email: "", password: "" });
}
componentDidMount() {
console.log("Email:" + this.state.email);
console.log("Password:" + this.state.password);
}
render() {
if (this.state.email == "") {
return <LogInForm />;
} else {
{
console.log(this.state);
}
return (
<div>Hello</div>,
<button onClick={this.logOut}>Log Out</button>
);
}
}
}
export default App;
The issue is haven't passed Login function in App file. This could be done in a more simple way, like this.
function LogInForm({ handleLogin, user, setUser }) {
return (
<form onSubmit={handleLogin}>
<div className="form-inner">
<h2>Login</h2>
{/* ERROR!*/}
<div className="form-group">
<label htmlFor="email">Email:</label>
<input
type="email"
name="email"
id="email"
onChange={(e) => setUser({ ...user, email: e.target.value })}
value={user.email}
/>
</div>
<div className="form-group">
<label htmlFor="password">Password:</label>
<input
type="password"
name="password"
id="password"
onChange={(e) => setUser({ ...user, password: e.target.value })}
value={user.password}
/>
</div>
<input type="submit" value="Log In" />
</div>
</form>
);
}
const adminUser = {
email: "admin#admin.com",
password: "test123"
};
export default function App() {
const [user, setUser] = useState({ name: "", email: "" });
const [isAuthenticated, setIsAuthenticated] = useState(false);
const handleLogin = () => {
if (
user.email === adminUser.email &&
user.password === adminUser.password
) {
console.log("Logged in!");
setIsAuthenticated(true);
} else {
console.log("details dont match");
}
};
const handleLogout = () => {
setIsAuthenticated(false);
console.log("Logout");
};
return (
<div className="Display">
{isAuthenticated ? (
<button onClick={handleLogout}>logout</button>
) : (
<LogInForm
handleLogin={handleLogin}
user={user}
setUser={setUser}
/>
)}
</div>
);
}

How to validate email and password using react hooks?

I am getting state values while clicking submit button but I am unable to do the validation for my login form and how to display the error messages below the input field when I enter my input wrong or empty. please give me a solution to this.Thanks in advance.
const Login = () => {
const [state, setState] = useState({
email: "",
password: ""
});
const handleChange = (e) => {
const {id, value} = e.target
setState(prevState => ({
...prevState,
[id]: value
}))
}
const handleSubmitClick = (e) => {
e.preventDefault();
console.log("Authenticated",state);
}
return(
<>
<div className="container">
<div className="title">
<form onSubmit={handleSubmitClick}>
<div className="form-group">
<input
type="email"
className="email"
placeholder="Email"
value={state.email}
onChange={handleChange}/>
</div>
<div className="form-group">
<input
type="password"
className="password"
placeholder="Password"
value={state.password}
onChange={handleChange}/>
</div>
<button type="submit" className="button">Enter</button>
</form>
</div>
</div>
</>
)
}
export default Login;
If you want to perform client-side validation, you can create hook like this:
const useEmailValidation = (email) => {
const isEmailValid = /#/.test(email); // use any validator you want
return isEmailValid;
};
And then you can use this hook in your form component:
...
const isEmailValid = useEmailValidation(state.email);
const isPasswordValid = usePasswordValidation(state.password);
const isFormValid = isEmailValid && isPasswordValid;
return (
...
<input
className={classNames({ 'invalid': !isEmailValid })}
type="email"
value={state.email}
onChange={handleChange}
/>
{!isEmailValid && 'Some error message'}
<button type="submit" disabled={!isFormValid} className="button">Enter</button>
...
);
...
Your validator hook can return validation message instead of boolean, like:
const useEmailValidation = (email) => {
if (!email || email.length === 0) {
return 'Email cannot be empty';
}
const isEmailValid = /#/.test(email); // use any validator you want
if (!isEmailValid) {
return 'Invalid email provided';
}
return null;
};
Also it is a good practice to show validation message only after field was focused before and after user tried to submit the form.
Formik is a great plugin that will help you perform form validation. The examples are also quite clear.
Or you could do something like this:
const Login = () => {
const [error, setError] = useState(null);
const [state, setState] = useState({
email: '',
password: '',
});
const validateEmail = (email) => {
const re =
/^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
};
const handleChange = (e) => {
const { id, value } = e.target;
setState((prevState) => ({
...prevState,
[id]: value,
}));
};
const handleSubmitClick = (e) => {
e.preventDefault();
if (!validateEmail(state.email)) {
setError('Invalid Email');
}
if (state.password.length < 8) {
setError('Password must be at least 8 chars long');
}
if (!error) {
// No errors.
}
};
return (
<>
<div className='container'>
<div className='title'>
{error && <div style={{ color: 'red' }}>{error}</div>}
<form onSubmit={handleSubmitClick}>
<div className='form-group'>
<input
type='email'
className='email'
placeholder='Email'
value={state.email}
onChange={handleChange}
/>
</div>
<div className='form-group'>
<input
type='password'
className='password'
placeholder='Password'
value={state.password}
onChange={handleChange}
/>
</div>
<button type='submit' className='button'>
Enter
</button>
</form>
</div>
</div>
</>
);
};
export default Login;
For an empty validation you can check it preventing the submit if the field is empty, like
const handleSubmitClick = (e) => {
e.preventDefault();
if(email.trim() === '' || password.trim() === ''){
//Add a h1 or section with the error message
}else{
console.log("Authenticated",state);
}
}
As long as the email field type is equal to email, which is your case, the browser should give an alert if the string is not an email. ("user#example.com")

How to use ComponentWillReceieveProp in React Hooks

I am trying to convert a class component into a functional component with hooks, but i am having a challenge on how to use an equivalent lifecycle of ComponentWillReceiveProps.
Log in form : Class Component:
class Login extends Component {
constructor() {
super();
this.state = {
emial:"",
password: "",
errors: {}
};
}
componentDidMount() {
window.history.pushState(null, document.title, window.location.href);
window.addEventListener('popstate', function (event){
window.history.pushState(null, document.title, window.location.href);
});
}
componentWillReceiveProps(nextProps) {// want to replace with hook equivalent
if (nextProps.auth.isAuthenticated) {
this.props.history.push("/recruitcentral"); // push user to dashboard when they login
}
if (nextProps.errors) {
this.setState({
errors: nextProps.errors
});
}
}
onChange = e => {
this.setState({ [e.target.id]: e.target.value });
};
onSubmit = e => {
e.preventDefault();
const userData = {
email: this.props.username,
password: this.state.password
};
this.props.loginUser(userData);
};
render() {
const { errors } = this.state;
return (
<>
<Row>
<Col>
<div>
<h1>Login</h1>
<div className={styles.formWrapper}>
<form onSubmit={this.onSubmit} noValidate>
<div className="email">
<label htmlFor="email"></label>
<input
onChange={this.onChange}
value={this.state.email}
error={errors.email}
id="email"
type="email"
className={classnames("", {
invalid: errors.email || errors.emailnotfound
})}
/>
<div className="Input__line" />
<label htmlFor="email" >Enter your email</label>
<span className="red-text">
{errors.email}
{errors.emailnotfound}
</span>
</div>
<div className="input-field col s12">
<input
onChange={this.onChange}
value={this.state.password}
error={errors.password}
id="password"
type="password"
className={classnames("", {
invalid: errors.password || errors.passwordincorrect
})}
/>
<div className="Input__line" />
<label htmlFor="password">Password</label>
<span className="red-text">
{errors.password}
{errors.passwordincorrect}
</span>
</div>
<div className={styles.createAccount}>
<button type="submit">Log in</button>
</div>
<div className={styles.createAccount}>
<button onClick={()=> this.props.history.push("/register")}type="submit">Create Account</button>
</div>
</form>
</div>
</div>
</Col>
</Row>
</>
Login Form :Functional Component
function Login(props) {
const [inputs, setInputs] = useState({
email:'',
password: '',
errors: {}
});
const [submitted, setSubmitted] = useState(false);
const { username, password } = inputs;
const auth =useSelector(state=>state.auth)
const dispatch = useDispatch();
const location = useLocation();
// reset login status
useEffect(() => {
dispatch(logoutUser());
}, []);
useEffect(() => {
// code to perform same function as componentWillReceiveProps here
}
}, [])
I know useEffect() will be used, but i really dont know how to implement it
componentWillRecieveProps is for all practical purposes deprecated with the recommendation being to use the componentDidUpdate lifecycle method. useEffect hook with appropriate dependency is the functional component equivalent.
For the first side-effect, navigating to "/recruitcentral" when user is authenticated.
useEffect(() => {
if (auth.isAuthenticated) {
props.history.push("/recruitcentral");
}
}, [auth]); // <-- Note: may complain about missing dependency like `history`, you can add these

React form with useState for multiple values

I have a demo here
It's a simple react form with typescript
Is it possible to use useState to capture the input fields data when the fields are completed
The form isn't just one piece of data but 3 - username, password and email
I could do it for one value with
onChange={e => setContact(e.target.value)}
but can I do it for 3 separate values
You can do it using computed properties.
First add a name property to each of your input, with values "username", "password", "email", then:
onChange={e => setContact(contact => ({...contact, [e.target.name]: e.target.value}))}
Edit: in React versions before 17, events are pooled and setContact update function is running asynchronously, so the event needs to be persisted this way:
onChange={e => {
e.persist();
setContact(contact => ({...contact, [e.target.name]: e.target.value}));
}}
Your handler could refer to the input name which you could then use to update the form object. There's a couple of issues with your syntax for this on the stackblitz - i.e the state object should be an object, not an array, so here's a full example:
const App = () => {
interface IForm {
username: string;
password: string;
email: string;
}
const [form, setFormValue] = useState<IForm>({
username: "",
password: "",
email: ""
});
const updateForm = (
formKey: keyof IForm,
event: React.ChangeEvent<HTMLInputElement>
) => {
const { value } = event.target;
setFormValue({
...form,
[formKey]: value
});
};
const handelSubmit = () => {
console.log("Form Submitted! Values: ", form);
};
return (
<div>
<form onSubmit={handelSubmit}>
<div>
<input
type="text"
placeholder="username"
value={form.username}
onChange={e => updateForm("username", e)}
/>
</div>
<div>
<input
type="password"
placeholder="password"
value={form.password}
onChange={e => updateForm("password", e)}
/>
</div>
<div>
<input
type="email"
placeholder="email"
value={form.email}
onChange={e => updateForm("email", e)}
/>
</div>
<input className="submit" type="submit" value="submit" />
</form>
</div>
);
};
You can create a object in the state and update the state with cloned object.
Here you go, Code with results as you expect:
import React, { useState } from "react";
import { render } from "react-dom";
import './style.css'
const App = () => {
interface IFrom {
username: string;
password: string;
email: string;
}
const [contact, setContact] = useState<IFrom[]>({
username:"",
email:"",
password:"",
});
console.log(contact)
const handelSubmit = () =>
{
axios.post(contact) //Example
};
return (
<div>
<form onSubmit={handelSubmit}>
<div>
<input
type="text"
placeholder="username"
value={contact.usename}
onChange={e => setContact({...contact, username: e.target.value})}
/>
</div>
<div>
<input
type="password"
placeholder="password"
onChange={e => setContact({...contact, password: e.target.value})}
/>
</div>
<div>
<input
type="email"
placeholder="email"
onChange={e => setContact({...contact, email: e.target.value})}
/>
</div>
<input className='submit' type="submi" value='submit'/>
</form>
</div>
);
};
render(<App />, document.getElementById("root"));

Resources