Form automatically submitting, why is preventDefault() not working - reactjs

I recently created a link shortening application in react. I have a problem with the form automatically submitting when I enter a link, I have preventDefault() as you can see in the code and onSubmit in the form element.
const onSubmit = (e) => {
e.preventDefault();
return false;
}
if (!text){
} else {
const shortenLink = async () => {
const res = await fetch(`https://api.shrtco.de/v2/shorten?url=${text}`)
const data = await res.json()
console.log(data.result);
setLinks(data.result)
setText('')
}
shortenLink()
}
const copy = () => {
navigator.clipboard.writeText(links.full_short_link)
setButtonText('Copied!')
}
return (
<>
<Container>
<StyledInput onSubmit={onSubmit}>
<InputContainer>
<Input type="url" value={text} onChange={(e) => setText(e.target.value)} placeholder="Shorten link here..." required />
<ButtonTwo onClick={onSubmit} type="submit" bg="#666AF6" color="#fff">Shorten It!</ButtonTwo>
</InputContainer>
</StyledInput>

Related

ReactJs form not displaying in browser even if the code runs with no erros

I wrote this code for a form to collect images and text data, it runs fine with no errors but in the browser, nothing is displayed but a blank screen.
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useDropzone } from 'react-dropzone';
const Form = () => {
const { register, handleSubmit } = useForm();
const [images, setImages] = useState([]);
const { getRootProps, getInputProps } = useDropzone({
accept: 'image/*',
onDrop: acceptedImages => {
setImages(acceptedImages.map(image => Object.assign(image, {
preview: URL.createObjectURL(image)
})));
}
});
const onSubmit = async data => {
const formData = new FormData();
images.forEach(image => {
formData.append('images', image);
});
formData.append('name', data.name);
formData.append('description', data.description);
try {
const response = await fetch('http://localhost:8000/submit-form', {
method: 'POST',
body: formData
});
console.log(response);
} catch (err) {
console.error(err);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
<br />
{images.map(image => (
<img key={image.name} src={image.preview} alt={image.name} style={{ width: '200px' }} />
))}
<br />
<input name="name" ref={register} placeholder="Name" />
<br />
<textarea name="description" ref={register} placeholder="Description" />
<br />
<button type="submit">Submit</button>
</form>
);
}
export default Form
I expected to see a form in the browser and at least see if it actually works but i saw none. I'm using react Dropzone and react hook form on the form. And maybe a fetch for the data.
Try to change the ref in input and textarea tag like so:
<input name="name" {...register('name')} placeholder="Name" />
<textarea name="description" {...register('description')} placeholder="Description" />
Reference: https://react-hook-form.com/get-started/
I suspect you haven't called the e.preventDefault() to prevent the default form submission. So, you may try the following:
Replace the statement:
<form onSubmit={handleSubmit(onSubmit)}>
to
<form onSubmit={handleSubmit}>
change the handleSubmit function to:
const onSubmit = e => {
e.preventDefault(); //prevent submit form
let form = e.target; //get the form obj
const formData = new FormData();
images.forEach(image => {
formData.append('images', image);
});
formData.append('name', form.name.value);
formData.append('description', form.description.value);
..............................
}

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 can I show the updated state?

When I'll click submit, it will save in the firestore, however, I still need to reload the page to update the state. How can I code it in a way that once I'll click submit, it will immediately reflect on the screen without reloading it?
const Account = () => {
const [displayName, setdisplayName] = useState(currentUser.displayName);
const [address, setAddress] = useState(currentUser.address);
const handleSubmit = async (event) => {
event.preventDefault();
try {
const userRef = firestore.collection("users").doc(currentUser.id);
const res = userRef.set(
{
displayName,
address,
},
{ merge: true }
);
} catch (err) {
console.log(err);
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<TextField
placeholder={currentUser.displayName}
value={displayName}
color="secondary"
required
onChange={(e) => setdisplayName(e.target.value)}
/>
<TextField
type="text"
placeholder={currentUser.address}
value={address}
onChange={(e) => setAddress(e.target.value)}
required
/>
<Button type="submit">Submit</Button>
</form>
</div>
);
};
export default MyAccount;
You can use 1 flag to mark when it submit,use useState update value of flag and page will change.
const Account = () => {
const [displayName, setdisplayName] = useState(currentUser.displayName);
const [address, setAddress] = useState(currentUser.address);
const [flag,setFlag] = useState(false);
const handleSubmit = async (event) => {
event.preventDefault();
try {
const userRef = firestore.collection("users").doc(currentUser.id);
const res = userRef.set(
{
displayName,
address,
},
{ merge: true }
);
setFlag(true);
} catch (err) {
console.log(err);
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<TextField
placeholder={currentUser.displayName}
value={displayName}
color="secondary"
required
onChange={(e) => setdisplayName(e.target.value)}
/>
<TextField
type="text"
placeholder={currentUser.address}
value={address}
onChange={(e) => setAddress(e.target.value)}
required
/>
<Button type="submit">Submit</Button>
</form>
</div>
);
};
export default MyAccount;

useState updated state not available in the same handler even with timeout [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed last year.
I have a simple registration form with 3 fields. I have stored the state in formValues with value & error associated with each field. Now when i submit the form without filling any or at least one field the form should be invalid but instead it shows validation messages with invalid fields but makes form valid. Even if i have added setTimeout the updated state is not available in the same handleSubmit. If i submit again the process works just fine. I understand that the state updation is async but if we see the logs in console the form's validation message is logged after formValues log in the render and those logs show that the state was updated correctly but the final validation message shows invalid state. If i change it to class component it works. Here's a link to codesandbox.
import React, { useState } from "react";
import { Button, Form, Col } from "react-bootstrap";
const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout));
const RegistrationForm = () => {
const [formValues, setFormValues] = useState({
name: { value: "", error: null },
email: { value: "", error: null },
password: { value: "", error: null }
});
const handleInputChange = (e, field) => {
const { value } = e.target;
setFormValues(prevValues => ({
...prevValues,
[field]: { value, error: null }
}));
};
const validateForm = () => {
let updatedFormValues = { ...formValues };
Object.keys(formValues).forEach(field => {
if (!formValues[field].value) {
updatedFormValues = {
...updatedFormValues,
[field]: { ...updatedFormValues[field], error: "required" }
};
}
});
setFormValues(updatedFormValues);
};
const isFormValid = () =>
Object.keys(formValues).every(field => formValues[field].error === null);
const handleSubmit = async e => {
e.preventDefault();
validateForm();
await sleep(100);
if (!isFormValid()) {
console.log("form is not valid", formValues);
return;
}
console.log("form is valid", formValues);
// make api call to complete registration
};
console.log({ formValues });
return (
<Form className="registration-form" onSubmit={handleSubmit}>
<Form.Row>
<Col>
<Form.Group controlId="name">
<Form.Label>Name</Form.Label>
<Form.Control
type="text"
placeholder="Enter name"
value={formValues.name.value}
onChange={e => handleInputChange(e, "name")}
/>
<Form.Control.Feedback type="invalid" className="d-block">
{formValues.name.error}
</Form.Control.Feedback>
</Form.Group>
</Col>
<Col>
<Form.Group controlId="email">
<Form.Label>Email</Form.Label>
<Form.Control
type="email"
placeholder="Enter email"
value={formValues.email.value}
onChange={e => handleInputChange(e, "email")}
/>
<Form.Control.Feedback type="invalid" className="d-block">
{formValues.email.error}
</Form.Control.Feedback>
</Form.Group>
</Col>
</Form.Row>
<Form.Row>
<Col>
<Form.Group controlId="password">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
placeholder="Enter password"
value={formValues.password.value}
onChange={e => handleInputChange(e, "password")}
/>
<Form.Control.Feedback type="invalid" className="d-block">
{formValues.password.error}
</Form.Control.Feedback>
</Form.Group>
</Col>
<Col />
</Form.Row>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
);
};
export default RegistrationForm;
State updates are not just async but are als affected by closures in functional components, so using a sleep or timeout isn't going to leave your with an updated value in the same render cycle
You can read more about it in this post:
useState set method not reflecting change immediately
However, one solution in your case is to maintain a ref and toggle is value to trigger a useEffect in which you will validate the form post handleSubmit handler validates it and sets the formValues
Relevant code:
const validateFormField = useRef(false);
const handleInputChange = (e, field) => {
const { value } = e.target;
setFormValues(prevValues => ({
...prevValues,
[field]: { value, error: null }
}));
};
const validateForm = () => {
let updatedFormValues = { ...formValues };
Object.keys(formValues).forEach(field => {
if (!formValues[field].value) {
updatedFormValues = {
...updatedFormValues,
[field]: { ...updatedFormValues[field], error: "required" }
};
}
});
setFormValues(updatedFormValues);
validateFormField.current = !validateFormField.current;
};
const isFormValid = () =>
Object.keys(formValues).every(field => formValues[field].error === null);
const handleSubmit = async e => {
e.preventDefault();
validateForm();
// make api call to complete registratin
};
useEffect(() => {
if (!isFormValid()) {
console.log("form is not valid", formValues);
} else {
console.log("form is valid", formValues);
}
}, [validateFormField.current]); // This is fine since we know setFormValues will trigger a re-render
Working demo

Problem with fetching API with user input

I'm building a search form where you can search for a city/country. I'm getting a response, but it adds localhost to the url
http://localhost:3000/api.geonames.org/searchJSON?q=london&username=username
Which it shouldn't do... What I'm I doing wrong?
state = {
text: ""
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
componentDidMount() {
const endpoint = `${api}q=${this.state.text}&username=${userName}`
console.log(endpoint)
fetch(endpoint)
.then(res => {
console.log(res)
})
}
handleSubmit = (e) => {
e.preventDefault()
this.setState({text: ""})
this.componentDidMount()
}
render() {
return (
<div>
<h1>CityPop</h1>
<form onSubmit={this.handleSubmit}>
<h3>Search by city</h3>
<input
type="search"
name="text"
value={this.state.text}
onChange={this.handleChange}
/>
<button>Search city</button>
</form>
</div>
)
}
Just add http/https protocol before the link:
const endpoint = `https://${api}q=${this.state.text}&username=${userName}`;

Resources