I am using react-validation to validate a login form. After submitting the form I want to know has an error or not. I can get the error state inside my custom button object in below way.
const customSubmit = ({ hasErrors, ...props }) => {
return <Button disabled={hasErrors} >Login</Button>
{hasErrors.toString()}/>
};
const SubmitButton = button(customSubmit);
Is there any way to get hasError state when submit a from?
I can share a similar code when I use State and handleChange to get the errors in the button.
First you need a function were you declare the useState.
import React from "react";
import { Box, Button, Collapse, Grid, TextField } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
function FormVal() {
const [phone, setPhone] = React.useState<string>();
const [errors, setErrors] = React.useState<{ phone: string }>();
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const {
target: { value },
} = event;
setErrors({ phone: "" });
setPhone(value);
let reg = new RegExp(/^\d*$/).test(value);
if (!reg) {
setErrors({ phone: "Only numbers" });
}
};
Now in the return section you need to declare the handleChange. There may be some elements that you don't know such as and that is because I am using Material-ui.
return (
<Box className={classes.root} width="100%" padding="5px">
<Grid container spacing={3}>
<Grid item xs={12}>
<TextField
id="outlined-basic"
autoComplete="off"
value={phone}
label="phone number"
inputProps={{ maxLength: 255 }}
onChange={handleChange}
required
error={Boolean(errors?.phone)}
helperText={errors?.phone}
variant="outlined"
/>
</Grid>
<Collapse in={phone?.length! > 0}>
<div style={{ width: "100%" }}>
<Button variant="contained">Save</Button>
</div>
</Collapse>
</Grid>
</Box>
);
}
export default FormVal;
Related
i'm quite new with react and i'm building a form with react-hook and useState to manage my datas after the submit.
I'm not able to use textfield as they are blocked. I think that i make some errors into value/onChange parameters but i don't know what type of error.
import React, { useState } from "react";
import {
TextField,
MenuItem,
Typography,
Checkbox,
Divider,
Button,
} from "#mui/material";
import { MdError } from "react-icons/md";
import { BsArrowRight } from "react-icons/bs";
import "../style/contactform.scss";
import { useForm } from "react-hook-form";
const initialState = {
name: "",
email: "",
};
const ContactForm = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const [state, setState] = useState(initialState);
const { name, email } = state;
const handleInputChange = (e) => {
const { name, value } = e.target;
setState({ ...state, [name]: value });
};
const onSubmit = (e) => {
e.preventDefault();
console.log("form submit");
setState(initialState);
};
return (
<form className="contact-form" onSubmit={handleSubmit(onSubmit)}>
<Typography variant="h4" className="form-title">
Be the first.
</Typography>
<div className="form-component">
<TextField
id="standard-basic"
label="Nome*"
variant="standard"
name="nome"
value={name}
onChange={handleInputChange}
{...register("nome", {
required: true,
})}
/>
{errors?.nome?.type === "required" && (
<MdError className="form-validation-icon" />
)}
</div>
<Divider className="form-hr" />
<div className="form-component">
<TextField
id="standard-basic"
label="Email*"
variant="standard"
name="email"
value={email}
onChange={handleInputChange}
{...register("email", {
required: true,
pattern: {
value:
/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
},
})}
/>
{errors?.email?.type === "required" && (
<MdError className="form-validation-icon" />
)}
{errors?.email?.type === "pattern" && (
<Typography variant="p" className="form-validation-email">
Inserisci un indirizzo email valido.
</Typography>
)}
</div>
<Divider className="form-hr" />
<Button className="form-submit" type="submit" variant="contained">
<BsArrowRight />
</Button>
</form>
);
};
export default ContactForm;
Textfields are completely block but initial state is actually working, do i miss something?
Can you help me?
To assign initial values using the useForm hook, you pass it under the defaultValues parameter passed to the hook like so:
const {
register,
handleSubmit,
reset
formState: { errors },
} = useForm({
defaultValues: initialState
});
Then just pass the ...register name and email to the inputs. There is no need to assign values to them again:
<TextField
id="standard-basic"
label="Name*"
variant="standard"
name="name"
{...register("name", {
required: true,
})}
/>
// for the email..
<TextField
id="standard-basic"
label="Email*"
variant="standard"
name="email"
{...register("email", {
required: true,
pattern: {
value: /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,},
})}
/>
If you'll notice, the values are off the text fields already and there's also no need for the handleInputChange function. The useForm hook takes care of that.
Edit:
In addition to the onSubmit function, the handleSubmit from useForm passes a data object into the function like this:
const onSubmit = (data) => {
console.log("form submitted", data);
reset(); // this can be destructured of the `useForm` hook.
};
For more info check their documentation
I have small React form to upload a file. I ask for the file name, a contact email, and the file itself. However, when I do not upload anything to the file input, I am still able to submit the form despite making the input file tag required. Why am I still able to submit this form and what can I do to prevent a user from being able to submit the form without uploading a file?
In contrast, when I leave the dataset name or contact email field blank, I am unable to submit because it says "Required"
For reference, I am using Material-UI for styling.
import React from 'react';
import { Form } from 'react-final-form';
import { TextField } from 'mui-rff';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import Grid from '#material-ui/core/Grid';
import Input from '#material-ui/core/Input';
import { FormControl } from '#material-ui/core';
import axios from 'axios';
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
padding: theme.spacing(2)
},
}));
const validate = values => {
const errors = {};
if (!values.datasetName) {
errors.datasetName = 'Required';
}
if (!values.contactEmail) {
errors.contactEmail = 'Required';
}
if (!values.uploadFile) {
errors.uploadFile = 'Required';
}
return errors;
};
const UploadForm = () => {
const classes = useStyles();
const [uploadFile, setUploadFile] = React.useState();
const onSubmit = async values => {
const dataArray = new FormData();
dataArray.append('uploadFile', uploadFile);
console.log('Submitting subscribe form!');
console.log('Subscribe form values:', values);
axios
.post("api_url_here", dataArray, {
headers: {
"Content-Type": "multipart/form-data"
}
})
.then((response) => {
console.log('Successfully uploaded file.');
})
.catch((error) => {
console.log('Unsuccessfully uploaded file.');
});
};
return (
<div className={classes.root}>
<Form
onSubmit={onSubmit}
initialValues={{ datasetName: 'dataset.csv', contactEmail: 'johndoe#example.com', uploadFile: 'sample.csv' }}
validate={validate}
render={({ handleSubmit, form, submitting, pristine, values }) => (
<form onSubmit={handleSubmit} noValidate>
<Grid
container
justify="center"
alignItems="center"
spacing={2}>
<Grid item xs={12}>
<TextField
label="Dataset Name"
name="datasetName"
margin="none"
required={true}
/>
</Grid>
<Grid item xs={12}>
<TextField
label="Contact Email"
name="contactEmail"
margin="none"
required={true}
/>
</Grid>
<Grid item xs={12}>
<FormControl>
<Input
name='uploadFile'
type='file'
required={true}
onChange={(e) => setUploadFile(e.target.files)}
/>
</FormControl>
</Grid>
<Grid item xs={12}>
<Button
variant="contained"
color="primary"
type="submit"
disabled={submitting}
>
Submit
</Button>
</Grid>
</Grid>
</form>
)}
/>
</div>
);
}
export default UploadForm;
Working on a tutorial atm that involves react material-ui tables that also has a search input textfield. What I am trying to add to it, is a button that will reset the table report but also clear the search input textfield.
It is the clearing of the search textfield that I am having trouble with.
They are using this code as a separate component library called Controls.input:
import React from 'react'
import { TextField } from '#material-ui/core';
export default function Input(props) {
const { name, label, value,error=null, onChange, ...other } = props;
return (
<TextField
variant="outlined"
label={label}
name={name}
value={value}
onChange={onChange}
{...other}
{...(error && {error:true,helperText:error})}
/>
)
}
The main search code is as follows where I have also added a button
<Controls.Input
id="name"
label="Search Name"
className={classes.searchInput}
InputProps={{
startAdornment: (<InputAdornment position="start">
<Search />
</InputAdornment>)
}}
onChange={handleSearch}
/>
<Button
onClick={handleClear}
className="materialBtn"
>
Clear
</Button>
At this point, I am not sure how to reference/target the search input field as part of the handleClear function, in-order to clear it's contents?
const handleClear = () => {
????
}
Do I need to use useState()?
You are right with having to put the value into state. Based on what you have supplied it seems that your state needs to be in your parent component. So something like this should work
import { useState } from 'react'
const ParentComponent = () => {
const [value, setValue] = useState('')
const handleClear = () => {
setValue('')
}
const handleSearch = (event) => {
setValue(event.target.value)
}
return (
<>
<Controls.Input
id="name"
label="Search Name"
className={classes.searchInput}
value={value}
onChange={handleSearch}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Search />
</InputAdornment>
),
}}
/>
<Button onClick={handleClear} className="materialBtn">
Clear
</Button>
</>
)
}
I have a registration page created in ReactJS. One of the fields is a checkbox isadult. When I click on Register button and save the fields in a database (MongoDB), the value of isadult appears as [Object object] instead of a concrete value: True or False.
What am I doing wrong?
import React from 'react';
import { Paper, makeStyles, Grid, TextField, Button, Switch } from '#material-ui/core';
import config from '../../config/config.json';
import axios from 'axios';
const useStyles = makeStyles((theme) => ({
root: {
minWidth: '300px',
width: '50%',
padding: '20px 20px 20px 20px',
margin: 'auto'
}
}));
const Register = () => {
const classes = useStyles();
const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState('');
const [isadult, setIsAdult] = React.useState('');
const handleChangeIsAdult = (event) => {
setIsAdult({
...isadult,
[event.target.name]: event.target.value,
});
}
const handleRegister = () => {
if (username && password) {
const formData = new FormData();
formData.append('username', username);
formData.append('password', password);
formData.append('isadult', isadult);
axios.post(config.api.url + '/auth/register', formData)
.then(res => {
console.log(res)
})
.catch(err => {
console.log(err)
})
}
}
return (
<Paper className={classes.root}>
<div >
<Grid container spacing={8} alignItems="flex-end">
<Grid item md={true} sm={true} xs={true}>
<TextField
id="username"
label="Username"
type="email"
fullWidth
value={username}
onChange={(e) => setUsername(e.target.value)}
autoFocus />
</Grid>
</Grid>
<Grid container spacing={8} alignItems="flex-end">
<Grid item md={true} sm={true} xs={true}>
<TextField
id="password"
label="Password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
fullWidth />
</Grid>
</Grid>
<Grid container spacing={8} alignItems="flex-end">
<Grid item md={true} sm={true} xs={true}>
<label>Is adult?</label>
<input
type='checkbox'
onChange={(event) => {
handleChangeIsAdult({
target: {
name: event.target.name,
value: event.target.checked,
},
});
}}
/>
</Grid>
</Grid>
<Grid container justify="center" style={{ marginTop: '10px' }}>
<Button
variant="outlined"
color="primary"
style={{ textTransform: "none" }}
onClick={handleRegister}
>
Register
</Button>
</Grid>
</div>
</Paper>
);
}
export default Register;
As your isadult state property is intended for use in the checkbox input, it needs to be declared and updated as a boolean.
You are declaring the propery as follows:
const [isadult, setIsAdult] = React.useState('');
The initial assignment is the empty string (''), which may cause issues with other places in your application code if they expect that property to be of type boolean. What you should do instead is start it off as a boolean:
const [isadult, setIsAdult] = React.useState(false);
Now, the main problem you are facing is the fact that the form data is being serialized with isadult being an object. This problem is coming from the fact that you are assigning an object to it through setIsAdult:
setIsAdult({
...isadult,
[event.target.name]: event.target.value,
});
The state property setter appends whatever value is passed to it directly to the property it is attached to. It works differently from setState that expects a state object. The right way to use the method in this case is:
setIsAdult(event.target.value);
Here, event.target.value contains exactly the checkbox checked boolean value that should go into isadult. Now, this property is serialized correctly as a boolean in your formData.
I have a field where a user can input there email address, however i also have a button underneath that field that says 'Add more', I am struggling to find a way to store the email value and have the user input another email, I am using React hooks for this process, for instance I understand that i would need to store the value of the email inside a
const [email, setEmail] = useState=();
below is my code relating to the problem
<Grid item xs={12} sm={6}>
<TextField
className={classes.field}
id="contactEmails"
name="contactEmails"
label="Contact Email(s)"
fullWidth
autoComplete="lname"
inputProps={{
maxLength: 250
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Button className={classes.addButton} variant='contained' color='primary'> Add Another Email</Button>
</Grid>
You don't want to have one stateful email but rather emails (an array of emails):
const [emails, setEmails] = useState([]);
To then add an email, take the previous emails and add one:
setEmails([...emails, "new.email#example.com"]);
Replacing an email works the same way:
setEmails(emails.map(prev => prev === "previous#example.com" ? "new#example.com" : prev));
Now you have to render all those emails too, for that you can map the array of emails into an array of components and return that from your component:
return emails.map(email => <p> {email} </p>);
You might want to have a look at useReducer for this instead though, that'll be far more elegant...
Here you have another option already working: https://jsfiddle.net/hpuey7zn/
Explanation:
In the state you store the current input value (see function handleChange) and an array of emails
onClick button adds the current email to the array if it is not included (see handleClick).
class App extends React.Component {
state = {
input: '',
emails: []
}
handleChange = e => {
this.setState({ input: e.target.value });
}
handleClick = ev => {
ev.preventDefault();
const { emails, input } = this.state;
if (!emails.includes(input))
this.setState({ emails: [...emails, input]});
}
render() {
return (
<div>
<input
name="email" label="email" type="email"
onChange={ this.handleChange }
/>
<button onClick={ev => { this.handleClick(ev)}}>
Add Another Email
</button>
<div>EMAILS:</div>
<ul>{this.state.emails.map(email => <li>{email}</li>)}</ul>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById("root"))
And here you have a version with hooks:
import React, { useState } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [input, setInput] = useState('');
const [emails, setEmails] = useState([]);
return (
<div>
<input name="email" label="email" type="email"
onChange={ ev => {setInput(ev.target.value)} }
/>
<button onClick={ev => {
if (!emails.includes(input)) setEmails([...emails, input])
}}>
Add Another Email
</button>
<div>EMAILS:</div>
<ul>{emails.map(e => <li>{e}</li>)}</ul>
</div>
)
}
const rootElement = document.getElementById("root");
if (rootElement) ReactDOM.render(<App />, rootElement);