Need little help on React hooks - reactjs

I try to make a todo list with React and Redux Toolkit. In handleSubmit function to add item on list, my setText somehow not set the value to empty string.
Here are my full code on Stackblitz: https://stackblitz.com/edit/react-ts-bqmjz1?file=components%2FTodoForm.tsx
const TodoForm = (): JSX.Element => {
//Input value
const [text, setText] = useState('');
const dispatch = useDispatch();
//setText not working
const handleSubmit = (e: any) => {
e.preventDefault();
if (text.trim().length === 0) {
return;
}
dispatch(addItem({ title: text }));
setText('');
};
return (
<form className="container-fluid" onSubmit={handleSubmit}>
<div className="input-group">
<input
className="form-control"
placeholder="Enter the value..."
onChange={(e: { target: HTMLInputElement }) =>
setText(e.target.value)
}
/>
{/* Submit button */}
<button type="submit" className="btn btn-primary">
Add
</button>
</div>
</form>
);
};

You're very close! You just missed the value prop on the input:
value={text}
const TodoForm = (): JSX.Element => {
//Input value
const [text, setText] = useState('');
const dispatch = useDispatch();
//setText not working
const handleSubmit = (e: any) => {
e.preventDefault();
if (text.trim().length === 0) {
return;
}
dispatch(addItem({ title: text }));
setText('');
};
return (
<form className="container-fluid" onSubmit={handleSubmit}>
<div className="input-group">
<input
className="form-control"
placeholder="Enter the value..."
value={text}
onChange={(e: { target: HTMLInputElement }) =>
setText(e.target.value)
}
/>
{/* Submit button */}
<button type="submit" className="btn btn-primary">
Add
</button>
</div>
</form>
);
};

If I understood well the input does not become empty when you click on the add button. To do that you just have to add value={text} in your input
<input
className="form-control"
placeholder="Enter the value..."
value={text}
onChange={(e: { target: HTMLInputElement }) =>
setText(e.target.value)
}
/>

Related

mock function error in react-testing-library

Hii Everyone I am new to react testing ,I am trying to do some example for practise , I am getting a Error,Need your help , this is my App Component
const[firstName,setFirstName]=useState("")
const[lastName,setLastName]=useState("")
const [data,setData] = useState({})
const handleFirstName = (e) =>{
setFirstName(e.target.value)
}
const handleLastName = (e) =>{
setLastName(e.target.value)
}
const handleSubmit = (e) =>{
e.preventDefault();
setData({firstName,lastName})
console.log(firstName,lastName)
}
return (
<div className="App">
<form onSubmit={handleSubmit} data-testid="form" >
<div>
<label>FirstName
<input type="text" name="firstName" id="firstName" value={firstName} onChange={handleFirstName}/>
</label>
</div>
{firstName && firstName.length > 10 && <p data-testid="error-msg" >FirstName is not valid</p>}
<div>
<label>lastName
<input type="text" name="lastName" id="lastName" value={lastName} onChange={handleLastName}/>
</label>
</div>
<button type="submit" name="submit" disabled={firstName === ""} >submit</button>
</form>
</div>
);
}
this is my testing logic
const mockFunction = jest.fn();
const {getByText}=render(<App onSubmit={mockFunction}/>)
const firstNameLabel = screen.getByRole("textbox",{'name':'FirstName'})
fireEvent.change(firstNameLabel,{"target":{'value':"dhffssß"}})
const lastNameLabel = screen.getByRole("textbox",{"name":"lastName"})
fireEvent.change(lastNameLabel,{"target":{'value':"dhfffsß"}})
const btn = screen.getByRole('button',{'name':'submit'})
fireEvent.click(btn)
expect(mockFunction).toHaveBeenCalledTimes(1)
})
I am testing simple form but getting this error
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
Try to call onSubmit function which you pass in props in your handleSubmit handler. In this case you should be able to track if the onSubmit callback was called in your tests.
const App = ({ onSubmit }) => {
const[firstName,setFirstName]=useState("")
const[lastName,setLastName]=useState("")
const [data,setData] = useState({})
const handleFirstName = (e) =>{
setFirstName(e.target.value)
}
const handleLastName = (e) =>{
setLastName(e.target.value)
}
const handleSubmit = (e) =>{
e.preventDefault();
setData({firstName,lastName});
console.log(firstName,lastName);
onSubmit();
}
return (
<div className="App">
<form onSubmit={handleSubmit} data-testid="form" >
<div>
<label>FirstName
<input type="text" name="firstName" id="firstName" value={firstName} onChange={handleFirstName}/>
</label>
</div>
{firstName && firstName.length > 10 && <p data-testid="error-msg" >FirstName is not valid</p>}
<div>
<label>lastName
<input type="text" name="lastName" id="lastName" value={lastName} onChange={handleLastName}/>
</label>
</div>
<button type="submit" name="submit" disabled={firstName === ""} >submit</button>
</form>
</div>
);
}
}
You may need to wait until the submit button is enabled before trying to interact with it, after you changed the firstName input, and wait for the callback call (not sure the second wait is usefull) :
import { render, waitFor } from '#testing-library/react';
const mockFunction = jest.fn();
const {getByText}=render(<App onSubmit={mockFunction}/>)
const firstNameLabel = screen.getByRole("textbox",{'name':'FirstName'})
fireEvent.change(firstNameLabel,{"target":{'value':"dhffssß"}})
const lastNameLabel = screen.getByRole("textbox",{"name":"lastName"})
fireEvent.change(lastNameLabel,{"target":{'value':"dhfffsß"}})
// wait until button is enabled
await waitFor(() => expect(getByText('submit').toBeEnabled());
fireEvent.click(getByText('submit'));
await waitFor(() => expect(mockFunction).toHaveBeenCalled();

Email Validation in React Functional Component

Using React functional component, I'm trying to validate the email address. Below is my code.
Problem is when I tried to enter any text nothing happens, its not allowing any letters to be entered.
Below is my component code, Please help me out
const UpdateEmailAddress: React.FC<Step> = (props: Step) => {
const [customerEmail, setCustomerEmail] = useState<any>('');
const [checkValidEmail, setcheckValidEmail] = useState<boolean | null>(null);
const emailValidate = (value: any) => {
if (!validEmailRegex(value)) {
setcheckValidEmail(true);
} else {
setcheckValidEmail(false);
}
};
const handleChange = (e: any) => {
if (e.target.value) {
setCustomerEmail(e.target.value);
}
};
return (
<>
<div className="step container update-email-container pt-10" ref={props.domRef}>
<div className="row">
<div className="col-md-4">
<div className="mb-4">
<TextField
aria-label={content.emailAddress.header}
enableClearText
errormessage={content.emailAddress.emailError}
helptext={content.emailAddress.helptext}
id="emailAddress"
isValid={checkValidEmail}
label={content.emailAddress.emailAddress}
maxLength={0}
name="emailAddress"
tooltipText=""
type="email"
onChange={(e:any) => handleChange(e)}
value={customerEmail}
/>
</div>
<ButtonAction
onClick={emailValidate(customerEmail)}
className="load-more-button mb-0"
type="button"
aria-label={content.emailAddress.nextButton}
>
{content.emailAddress.nextButton}
</ButtonAction>
</div>
</div>
</div>
</>
);
};
Your logic in onChange method is wrong it should be
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setCustomerEmail(e.target.value)}
You have to define the onChange() method.
It should be like this
const handleChange = (e) => {
setCustomerEmail(e.target.value);
};
Then you will be able to enter textfield.
Secondly, you have to pass the value in emailvalidate() in onClick() of button.
<ButtonAction onClick={()=>emailValidate(customerEmail)} className="load-more-button mb-0" type="button" aria-label={content.emailAddress.nextButton}>
{content.emailAddress.nextButton}
</ButtonAction>
And remove e parameter in emailValidate(value) as it is only using value and not e.

Two-way data binding on large forms

(After working through a React.js tutorial, I'm currently coding my first little app to get more practice. So this will be a newbie question and the answer is certainly out there somewhere, but apparently I don't know what to search for.)
Google lists a lot of examples on how to achieve two-way data binding for one input field. But what about large, complex forms, possibly with the option of adding more dynamically?
Let's say my form consists of horizontal lines of input fields. All lines are the same: First name, last name, date of birth and so on. At the bottom of the table, there is a button to insert a new such line. All this data is stored in an array. How do I bind each input field to its respective array element, so that the array gets updated when the user edits a value?
Working example with two lines of two columns each:
import { useState} from 'react';
function App() {
var [name1, setName1] = useState('Alice');
var [score1, setScore1] = useState('100');
var [name2, setName2] = useState('Bob');
var [score2, setScore2] = useState('200');
function changeNameHandler1 (e) {
console.log(e)
setName1(e.target.value)
}
function changeScoreHandler1 (e) {
setScore1(e.target.value)
}
function changeNameHandler2 (e) {
setName2(e.target.value)
}
function changeScoreHandler2 (e) {
setScore2(e.target.value)
}
return (
<div>
<table>
<tbody>
<tr>
<td><input name="name1" id="id1" type="text" value={name1} onChange={changeNameHandler1} /></td>
<td><input name="score1" type="text" value={score1} onChange={changeScoreHandler1} /></td>
</tr>
<tr>
<td><input name="name2" type="text" value={name2} onChange={changeNameHandler2} /></td>
<td><input name="score2" type="text" value={score2} onChange={changeScoreHandler2} /></td>
</tr>
</tbody>
</table>
{name1} has a score of {score1}<br />
{name2} has a score of {score2}<br />
</div>
);
}
export default App;
How do I scale this up without having to add handler functions for hundreds of fields individually?
You can still store your fields in an object and then just add to the object when you want to add a field. Then map through the keys to display them.
Simple example:
import { useState } from 'react'
const App = () => {
const [fields, setFields ] = useState({
field_0: ''
})
const handleChange = (e) => {
setFields({
...fields,
[e.target.name]: e.target.value
})
}
const addField = () => setFields({
...fields,
['field_' + Object.keys(fields).length]: ''
})
const removeField = (key) => {
delete fields[key]
setFields({...fields})
}
return (
<div>
{Object.keys(fields).map(key => (
<div>
<input onChange={handleChange} key={key} name={key} value={fields[key]} />
<button onClick={() => removeField(key)}>Remove Field</button>
</div>
))}
<button onClick={() => addField()}>Add Field</button>
<button onClick={() => console.log(fields)}>Log fields</button>
</div>
);
};
export default App;
Here is what I think you are trying to achieve in your question:
import { useState } from 'react'
const App = () => {
const [fieldIndex, setFieldIndex] = useState(1)
const [fields, setFields ] = useState({
group_0: {
name: '',
score: ''
}
})
const handleChange = (e, key) => {
setFields({
...fields,
[key]: {
...fields[key],
[e.target.name]: e.target.value
}
})
}
const addField = () => {
setFields({
...fields,
['group_' + fieldIndex]: {
name: '',
score: ''
}
})
setFieldIndex(i => i + 1)
}
const removeField = (key) => {
delete fields[key]
setFields({...fields})
}
return (
<div>
{Object.keys(fields).map((key, index) => (
<div key={key}>
<div>Group: {index}</div>
<label>Name:</label>
<input onChange={(e) => handleChange(e, key)} name='name' value={fields[key].name} />
<label>Score: </label>
<input onChange={(e) => handleChange(e, key)} name='score' value={fields[key].score} />
<button onClick={() => removeField(key)}>Remove Field Group</button>
</div>
))}
<button onClick={() => addField()}>Add Field</button>
<button onClick={() => console.log(fields)}>Log fields</button>
</div>
);
};
export default App;
You may want to keep the index for naming in which case you can use an array. Then you would just pass the index to do your input changing. Here is an example of using an array:
import { useState } from 'react'
const App = () => {
const [fields, setFields ] = useState([
{
name: '',
score: ''
}
])
const handleChange = (e, index) => {
fields[index][e.target.name] = e.target.value
setFields([...fields])
}
const addField = () => {
setFields([
...fields,
{
name: '',
score: ''
}
])
}
const removeField = (index) => {
fields.splice(index, 1)
setFields([...fields])
}
return (
<div>
{fields.map((field, index) => (
<div key={index}>
<div>Group: {index}</div>
<label>Name:</label>
<input onChange={(e) => handleChange(e, index)} name='name' value={field.name} />
<label>Score: </label>
<input onChange={(e) => handleChange(e, index)} name='score' value={field.score} />
<button onClick={() => removeField(index)}>Remove Field Group</button>
</div>
))}
<button onClick={() => addField()}>Add Field</button>
<button onClick={() => console.log(fields)}>Log fields</button>
</div>
);
};
export default App;
have multiple solutions to this problem for example:
Solution #1
Using useRef to store value of the field
import { useRef, useCallback } from "react";
export default function App() {
const fullNameInputElement = useRef();
const emailInputElement = useRef();
const passwordInputElement = useRef();
const passwordConfirmationInputElement = useRef();
const formHandler = useCallback(
() => (event) => {
event.preventDefault();
const data = {
fullName: fullNameInputElement.current?.value,
email: emailInputElement.current?.value,
password: passwordInputElement.current?.value,
passwordConfirmation: passwordConfirmationInputElement.current?.value
};
console.log(data);
},
[]
);
return (
<form onSubmit={formHandler()}>
<label htmlFor="full_name">Full name</label>
<input
ref={fullNameInputElement}
id="full_name"
placeholder="Full name"
type="text"
/>
<label htmlFor="email">Email</label>
<input
ref={emailInputElement}
id="email"
placeholder="Email"
type="email"
/>
<label htmlFor="password">Password</label>
<input
ref={passwordInputElement}
id="password"
placeholder="Password"
type="password"
/>
<label htmlFor="password_confirmation">Password Confirmation</label>
<input
ref={passwordConfirmationInputElement}
id="password_confirmation"
placeholder="Password Confirmation"
type="password"
/>
<button type="submit">Submit</button>
</form>
);
}
or still use useState but store all values in one object
import { useState, useCallback } from "react";
const initialUserData = {
fullName: "",
email: "",
password: "",
passwordConfirmation: ""
};
export default function App() {
const [userData, setUserData] = useState(initialUserData);
const updateUserDataHandler = useCallback(
(type) => (event) => {
setUserData({ ...userData, [type]: event.target.value });
},
[userData]
);
const formHandler = useCallback(
() => (event) => {
event.preventDefault();
console.log(userData);
},
[userData]
);
return (
<form onSubmit={formHandler()}>
<label htmlFor="full_name">Full name</label>
<input
id="full_name"
placeholder="Full name"
type="text"
value={userData.fullName}
onChange={updateUserDataHandler("fullName")}
/>
<label>Email</label>
<input
id="email"
placeholder="Email"
type="email"
value={userData.email}
onChange={updateUserDataHandler("email")}
/>
<label htmlFor="password">Password</label>
<input
id="password"
placeholder="Password"
type="password"
value={userData.password}
onChange={updateUserDataHandler("password")}
/>
<label htmlFor="password_confirmation">Password Confirmation</label>
<input
id="password_confirmation"
placeholder="Password Confirmation"
type="password"
value={userData.passwordConfirmation}
onChange={updateUserDataHandler("passwordConfirmation")}
/>
<button type="submit">Submit</button>
</form>
);
}
Solution #2
Or you have multiple libraries that also provide solutions like react-form-hook https://react-hook-form.com/

Cntrl S to submit a from in react JS but input field not working

// When i try to sumbit a from by ctrl s it submit but problem is input field not work
// when i comment onKeyDown input filed work but that's time i can't submit form using ctrl S
const [value, setValue] = useState("");
const [contentEdit, setContentEdit] = useState(false);
const handleChange = e => {
setValue(e.target.value);
console.log(e.target.value);
setContentEdit(false);
};
const handleSubmit = e => {
e.preventDefault();
alert("you have searched for - " + value);
// or you can send to backend
setValue("");
};
const handleKeypress = event => {
event.preventDefault();
let charCode = String.fromCharCode(event.which).toLowerCase();
if ((event.ctrlKey || event.metaKey) && charCode === 's') {
console.log("CTRL+S Pressed");
alert("CTRL+S Pressed");
// onSubmitMemo();
handleSubmit();
}
};
<div
onKeyDown={handleKeypress}
contentEditable={true}
className='border border-3 border-info'>
<h1>Hello Ctrl + S to Submit</h1>
<div>
<input type="text" name="email"
onChange={handleChange}
/>
<input type="text" name="name"
onChange={handleChange}
/>
<button type="submit" onClick={handleSubmit}>+</button>
</div>
</div>
I have tried your code without the Ctrl + S it's working fine. But if you insert the contentEditable={true} props to div it doesn't work. But i have implemented for you. you can refer this.
import React from "react";
export default function App() {
const [value, setValue] = React.useState("");
const [name, setName] = React.useState("");
console.log(value, name);
const handleSubmit = (e) => {
alert("you have searched for - " + value);
// or you can send to backend
setValue("");
setName("");
};
const handleKeypress = (event) => {
let charCode = String.fromCharCode(event.which).toLowerCase();
if (event.ctrlKey && charCode === "s") {
console.log("CTRL+S Pressed");
alert("CTRL+S Pressed");
// onSubmitMemo();
handleSubmit();
}
};
return (
<div
onKeyDown={(e) => handleKeypress(e)}
className="border border-3 border-info"
>
<h1>Hello Ctrl + S to Submit</h1>
<div>
<form onSubmit={(e) => handleSubmit(e)}>
<input
type="text"
name="email"
onChange={(e) => setValue(e.target.value)}
value={value}
/>
<input
type="text"
name="name"
onChange={(e) => setName(e.target.value)}
value={name}
/>
<button type="submit" onClick={handleSubmit}>
+
</button>
</form>
</div>
</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")

Resources